vRIO builds in the vPLASMA display
The plasma glass now lives in vRIO's encoder strip: PlasmaCanvas grew a dot-pitch parameter (default keeps the standalone glass pixel-identical) and is compile-linked into VRio.App at pitch 3, parked at the strip's left slot. The axis gauges move to the grid's right edge, the strip is as tall as the glass, and the status text sits between them with the axis readout split onto two lines. The button grid also compacts: rows shift up past the layout's empty row 0 and the spare bottom row is trimmed (the shared CockpitLayout is untouched, so coordinates still match RIOJoy's map). Control strip: the COM row labels are now the device names -- vRIO and vPLASMA -- coloured by port status (green open, gray closed), replacing the port-status line. The plasma row auto-opens COM12 at startup like the RIO row does COM11; one Rescan refreshes both pickers. Plasma log lines share the wire log under a vPLASMA: prefix, and the standalone gestures carry over (double-click self-test, right-click reset). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+140
-38
@@ -1,5 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO.Ports;
|
||||
using VPlasma.App;
|
||||
using VPlasma.Core.Device;
|
||||
using VRio.Core.Device;
|
||||
using VRio.Core.Input;
|
||||
|
||||
@@ -8,11 +10,14 @@ namespace VRio.App;
|
||||
/// <summary>
|
||||
/// vRIO main window: the interactive cockpit panel on the left (the same
|
||||
/// functional map RIOJoy's profile editor shows) and a control strip on the
|
||||
/// right — COM port, device settings, and a live wire log. At startup the
|
||||
/// usual port (<see cref="PreferredPort"/>) is opened automatically when it's
|
||||
/// available; otherwise open a COM port by hand. Point RIOJoy at the other
|
||||
/// end of the null-modem pair, and every click here arrives at RIOJoy exactly
|
||||
/// as if the physical cockpit sent it.
|
||||
/// right — COM ports, device settings, and a live wire log. The panel's
|
||||
/// encoder strip also hosts the built-in plasma glass (the vPLASMA display
|
||||
/// emulator) on its own COM port. At startup the usual ports
|
||||
/// (<see cref="PreferredPort"/>, <see cref="PreferredPlasmaPort"/>) are
|
||||
/// opened automatically when available; otherwise open them by hand. Point
|
||||
/// RIOJoy at the other end of the null-modem pair, and every click here
|
||||
/// arrives at RIOJoy exactly as if the physical cockpit sent it; point the
|
||||
/// game's COM2 passthrough at the plasma pair and the glass lights up.
|
||||
/// </summary>
|
||||
internal sealed class MainForm : Form
|
||||
{
|
||||
@@ -23,9 +28,22 @@ internal sealed class MainForm : Form
|
||||
/// </summary>
|
||||
private const string PreferredPort = "COM11";
|
||||
|
||||
/// <summary>
|
||||
/// The built-in plasma's usual port: the device end of the COM2⇄COM12
|
||||
/// com0com pair (the game's COM2 passthrough opens the other end).
|
||||
/// </summary>
|
||||
private const string PreferredPlasmaPort = "COM12";
|
||||
|
||||
private readonly VRioDevice _device = new();
|
||||
private readonly VRioSerialService _service;
|
||||
private readonly VPlasmaDevice _plasmaDevice = new();
|
||||
private readonly VPlasmaSerialService _plasmaService;
|
||||
private readonly PanelCanvas _canvas = new();
|
||||
private readonly PlasmaCanvas _plasmaCanvas = new(PanelCanvas.PlasmaDotPitch)
|
||||
{
|
||||
Location = PanelCanvas.PlasmaSlot,
|
||||
};
|
||||
private int _selfTestPage;
|
||||
private readonly InputRouter _router;
|
||||
private readonly XInputGamepad _gamepad = new();
|
||||
private readonly KeyboardLampMirror _lampMirror;
|
||||
@@ -34,6 +52,16 @@ internal sealed class MainForm : Form
|
||||
private readonly string _bindingsPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "vRIO", "bindings.txt");
|
||||
|
||||
// Port rows: one line per built-in device — a name label whose colour is
|
||||
// the port status (green = open, gray = closed), the port picker, and an
|
||||
// Open/Close button. The one Rescan button refreshes both pickers.
|
||||
private readonly Label _rioLabel = new()
|
||||
{
|
||||
Text = "vRIO",
|
||||
Location = new Point(12, 15),
|
||||
AutoSize = true,
|
||||
ForeColor = Color.Gray,
|
||||
};
|
||||
private readonly ComboBox _portBox = new()
|
||||
{
|
||||
Location = new Point(80, 12),
|
||||
@@ -42,13 +70,20 @@ internal sealed class MainForm : Form
|
||||
};
|
||||
private readonly Button _rescan = new() { Text = "Rescan", Location = new Point(214, 11), Width = 52 };
|
||||
private readonly Button _openClose = new() { Text = "Open", Location = new Point(272, 11), Width = 46 };
|
||||
private readonly Label _linkStatus = new()
|
||||
private readonly Label _plasmaLabel = new()
|
||||
{
|
||||
Text = "Port closed.",
|
||||
Location = new Point(12, 42),
|
||||
Text = "vPLASMA",
|
||||
Location = new Point(12, 45),
|
||||
AutoSize = true,
|
||||
ForeColor = Color.Gray,
|
||||
};
|
||||
private readonly ComboBox _plasmaPortBox = new()
|
||||
{
|
||||
Location = new Point(80, 42),
|
||||
Width = 128,
|
||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||
};
|
||||
private readonly Button _plasmaOpenClose = new() { Text = "Open", Location = new Point(272, 41), Width = 46 };
|
||||
|
||||
private readonly CheckBox _spring = new() { Text = "Stick springs back to center", Location = new Point(10, 24), AutoSize = true, Checked = true };
|
||||
private readonly Button _centerAxes = new() { Text = "Center all axes", Location = new Point(10, 52), Width = 140, Height = 26 };
|
||||
@@ -112,7 +147,8 @@ internal sealed class MainForm : Form
|
||||
AutoSize = true,
|
||||
ForeColor = Color.Gray,
|
||||
Text = "Left-click a cell: momentary press. Right-click: latch it down. " +
|
||||
"Drag the X/Y box and the Z / L / R gauges to move the axes.",
|
||||
"Drag the X/Y box and the Z / L / R gauges to move the axes. " +
|
||||
"Double-click the plasma glass to cycle its self-test pages; right-click it to reset.",
|
||||
};
|
||||
|
||||
private readonly TextBox _logBox = new()
|
||||
@@ -153,11 +189,14 @@ internal sealed class MainForm : Form
|
||||
KeyPreview = true; // form-level key routing for the input bindings
|
||||
|
||||
_service = new VRioSerialService(_device);
|
||||
_plasmaService = new VPlasmaSerialService(_plasmaDevice);
|
||||
_router = new InputRouter(_device);
|
||||
_lampMirror = new KeyboardLampMirror(_device);
|
||||
|
||||
// Panel canvas, scrolled if the window is smaller than the grid.
|
||||
// Panel canvas, scrolled if the window is smaller than the grid. The
|
||||
// plasma glass rides along as a child in the encoder strip's left slot.
|
||||
var scroller = new Panel { Dock = DockStyle.Fill, AutoScroll = true, BackColor = Color.FromArgb(28, 28, 28) };
|
||||
_canvas.Controls.Add(_plasmaCanvas);
|
||||
scroller.Controls.Add(_canvas);
|
||||
|
||||
Controls.Add(scroller);
|
||||
@@ -186,8 +225,22 @@ internal sealed class MainForm : Form
|
||||
_service.HostHandshake += high => RunOnUi(() =>
|
||||
PrependLog(high ? "Host raised DTR (board-reset handshake)" : "Host dropped DTR"));
|
||||
|
||||
// Built-in plasma glass: a pure listener on its own port, same gestures
|
||||
// as the standalone vPLASMA — double-click cycles the self-test pages,
|
||||
// right-click resets the display to its power-on state.
|
||||
_plasmaDevice.Updated += () => RunOnUi(() => _plasmaCanvas.UpdateFrom(_plasmaDevice));
|
||||
_plasmaService.Logged += line => RunOnUi(() => PrependLog($"vPLASMA: {line}"));
|
||||
_plasmaService.ConnectionChanged += open => RunOnUi(() => OnPlasmaConnectionChanged(open));
|
||||
_plasmaCanvas.DoubleClick += (_, _) => RunPlasmaSelfTest();
|
||||
_plasmaCanvas.MouseClick += (_, e) =>
|
||||
{
|
||||
if (e.Button == MouseButtons.Right)
|
||||
_plasmaDevice.Reset();
|
||||
};
|
||||
|
||||
_rescan.Click += (_, _) => RefreshPorts();
|
||||
_openClose.Click += (_, _) => ToggleOpen();
|
||||
_plasmaOpenClose.Click += (_, _) => TogglePlasmaOpen();
|
||||
_spring.CheckedChanged += (_, _) => _canvas.StickSpringsBack = _spring.Checked;
|
||||
_centerAxes.Click += (_, _) =>
|
||||
{
|
||||
@@ -251,13 +304,15 @@ internal sealed class MainForm : Form
|
||||
_lampMirror.Dispose();
|
||||
_rawKeyboard.Dispose();
|
||||
_service.Dispose();
|
||||
_plasmaService.Dispose();
|
||||
};
|
||||
|
||||
RefreshPorts();
|
||||
UpdateStatus();
|
||||
PrependLog("vRIO ready. Open a COM port, then point RIOJoy at the other end of the pair.");
|
||||
LoadBindings();
|
||||
AutoOpenPreferredPort();
|
||||
_plasmaCanvas.UpdateFrom(_plasmaDevice); // paint the power-on frame
|
||||
AutoOpenPreferredPorts();
|
||||
}
|
||||
|
||||
private Panel BuildControlStrip()
|
||||
@@ -275,11 +330,13 @@ internal sealed class MainForm : Form
|
||||
BorderStyle = BorderStyle.FixedSingle,
|
||||
};
|
||||
|
||||
panel.Controls.Add(new Label { Text = "COM port:", Location = new Point(12, 15), AutoSize = true });
|
||||
panel.Controls.Add(_rioLabel);
|
||||
panel.Controls.Add(_portBox);
|
||||
panel.Controls.Add(_rescan);
|
||||
panel.Controls.Add(_openClose);
|
||||
panel.Controls.Add(_linkStatus);
|
||||
panel.Controls.Add(_plasmaLabel);
|
||||
panel.Controls.Add(_plasmaPortBox);
|
||||
panel.Controls.Add(_plasmaOpenClose);
|
||||
|
||||
var device = new GroupBox { Text = "Device", Location = new Point(12, 68), Size = new Size(306, 88) };
|
||||
device.Controls.AddRange(new Control[] { _spring, _centerAxes, _lampsOff });
|
||||
@@ -306,52 +363,80 @@ internal sealed class MainForm : Form
|
||||
|
||||
private void RefreshPorts()
|
||||
{
|
||||
string? current = _portBox.SelectedItem?.ToString();
|
||||
_portBox.Items.Clear();
|
||||
foreach (string name in SerialPort.GetPortNames().Distinct().OrderBy(n => n, StringComparer.OrdinalIgnoreCase))
|
||||
_portBox.Items.Add(name);
|
||||
string[] names = SerialPort.GetPortNames().Distinct()
|
||||
.OrderBy(n => n, StringComparer.OrdinalIgnoreCase).ToArray();
|
||||
// An open service's picker is disabled and shows the served port;
|
||||
// leave it alone so the display can't drift from reality.
|
||||
if (!_service.IsOpen)
|
||||
RefreshPicker(_portBox, names);
|
||||
if (!_plasmaService.IsOpen)
|
||||
RefreshPicker(_plasmaPortBox, names);
|
||||
}
|
||||
|
||||
if (_portBox.Items.Count == 0)
|
||||
private static void RefreshPicker(ComboBox box, string[] portNames)
|
||||
{
|
||||
string? current = box.SelectedItem?.ToString();
|
||||
box.Items.Clear();
|
||||
foreach (string name in portNames)
|
||||
box.Items.Add(name);
|
||||
|
||||
if (box.Items.Count == 0)
|
||||
return;
|
||||
int idx = current is null ? -1 : _portBox.Items.IndexOf(current);
|
||||
_portBox.SelectedIndex = idx >= 0 ? idx : 0;
|
||||
int idx = current is null ? -1 : box.Items.IndexOf(current);
|
||||
box.SelectedIndex = idx >= 0 ? idx : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Startup convenience: if <see cref="PreferredPort"/> exists, select it
|
||||
/// and try to open it. Failures (port missing, or busy because another
|
||||
/// vRIO/app holds it) just log — no modal box at launch — and leave the
|
||||
/// manual picker in charge.
|
||||
/// Startup convenience: if a device's usual port exists, select it and try
|
||||
/// to open it. Failures (port missing, or busy because another vRIO/app
|
||||
/// holds it) just log — no modal box at launch — and leave the manual
|
||||
/// pickers in charge.
|
||||
/// </summary>
|
||||
private void AutoOpenPreferredPort()
|
||||
private void AutoOpenPreferredPorts()
|
||||
{
|
||||
int idx = _portBox.Items.IndexOf(PreferredPort);
|
||||
AutoOpen(_portBox, PreferredPort, _service.Open);
|
||||
AutoOpen(_plasmaPortBox, PreferredPlasmaPort, _plasmaService.Open);
|
||||
}
|
||||
|
||||
private void AutoOpen(ComboBox box, string port, Action<string> open)
|
||||
{
|
||||
int idx = box.Items.IndexOf(port);
|
||||
if (idx < 0)
|
||||
{
|
||||
PrependLog($"{PreferredPort} not present — pick a port and open it manually.");
|
||||
PrependLog($"{port} not present — pick a port and open it manually.");
|
||||
return;
|
||||
}
|
||||
|
||||
_portBox.SelectedIndex = idx;
|
||||
box.SelectedIndex = idx;
|
||||
try
|
||||
{
|
||||
_service.Open(PreferredPort);
|
||||
open(port);
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidOperationException or ArgumentException)
|
||||
{
|
||||
PrependLog($"{PreferredPort} is present but could not be opened ({ex.Message.TrimEnd('.')}) — open it manually once it frees up.");
|
||||
PrependLog($"{port} is present but could not be opened ({ex.Message.TrimEnd('.')}) — open it manually once it frees up.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleOpen()
|
||||
{
|
||||
if (_service.IsOpen)
|
||||
{
|
||||
_service.Close();
|
||||
return;
|
||||
}
|
||||
else
|
||||
OpenFromPicker(_portBox, _service.Open);
|
||||
}
|
||||
|
||||
if (_portBox.SelectedItem?.ToString() is not { } port)
|
||||
private void TogglePlasmaOpen()
|
||||
{
|
||||
if (_plasmaService.IsOpen)
|
||||
_plasmaService.Close();
|
||||
else
|
||||
OpenFromPicker(_plasmaPortBox, _plasmaService.Open);
|
||||
}
|
||||
|
||||
private void OpenFromPicker(ComboBox box, Action<string> open)
|
||||
{
|
||||
if (box.SelectedItem?.ToString() is not { } port)
|
||||
{
|
||||
MessageBox.Show(this, "No COM port selected. On a single PC, install a com0com virtual " +
|
||||
"null-modem pair and open one end here.", "vRIO", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
@@ -360,7 +445,7 @@ internal sealed class MainForm : Form
|
||||
|
||||
try
|
||||
{
|
||||
_service.Open(port);
|
||||
open(port);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -372,12 +457,29 @@ internal sealed class MainForm : Form
|
||||
private void OnConnectionChanged(bool open)
|
||||
{
|
||||
_openClose.Text = open ? "Close" : "Open";
|
||||
_portBox.Enabled = _rescan.Enabled = !open;
|
||||
_linkStatus.Text = open ? $"Serving {_service.PortName} @ 9600 8N1." : "Port closed.";
|
||||
_linkStatus.ForeColor = open ? Color.ForestGreen : Color.Gray;
|
||||
_portBox.Enabled = !open;
|
||||
_rioLabel.ForeColor = open ? Color.ForestGreen : Color.Gray;
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
private void OnPlasmaConnectionChanged(bool open)
|
||||
{
|
||||
_plasmaOpenClose.Text = open ? "Close" : "Open";
|
||||
_plasmaPortBox.Enabled = !open;
|
||||
_plasmaLabel.ForeColor = open ? Color.ForestGreen : Color.Gray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Feed the next canned self-test page through the plasma's wire parser —
|
||||
/// the same end-to-end exercise the standalone vPLASMA runs on double-click.
|
||||
/// </summary>
|
||||
private void RunPlasmaSelfTest()
|
||||
{
|
||||
byte[] bytes = PlasmaSelfTest.BuildPage(_selfTestPage);
|
||||
_plasmaDevice.OnReceived(bytes, bytes.Length);
|
||||
_selfTestPage = (_selfTestPage + 1) % PlasmaSelfTest.PageCount;
|
||||
}
|
||||
|
||||
// ---- Keyboard / gamepad input -------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user