Editor cockpit rework + dormant-by-default serial port use
Profile editor: - Cockpit-style encoder gauges centered over the Upper Middle MFD: Z left, X/Y right, and L/R/Rz arranged as a big "U". - [JoyStick] section surfaced as a vertical "Axis" toggle column (6 inverts + ZR mix), written back to the profile's calibration. - Tighten the panel layout (close gaps, trim empty border columns); colour the Secondary/Screen columns yellow; rename the "Joystick / Hat" board to "Joystick"; add an "Import .ini..." tray entry. - "RIO commands (live)" button group (all RioCommandCodes) fired at the live RIO. Serial-port handling: - Start dormant; only take the COM port for a profiled game (auto-switch) or while editing, so the native games can open the port without clashing. - Editor sessions hold the port but route keyboard/mouse/joystick to no-op sinks (NullInputSink), so button function can be checked without injecting input; RioRuntime.ButtonActivity drives a live cyan press indicator and lamp feedback still applies. - AutoSwitchWatcher.Reset() re-syncs the watcher after an editor session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -70,23 +70,23 @@ public static class CockpitPanel
|
||||
{
|
||||
// Upper MFD row.
|
||||
new PanelGroup("Upper Left MFD", PanelGroupKind.Mfd, 4, 2, true, 0, 1, Mfd(0x2F)),
|
||||
new PanelGroup("Upper Middle MFD", PanelGroupKind.Mfd, 4, 2, true, 5, 1, Mfd(0x27)),
|
||||
new PanelGroup("Upper Right MFD", PanelGroupKind.Mfd, 4, 2, true, 10, 1, Mfd(0x37)),
|
||||
new PanelGroup("Upper Middle MFD", PanelGroupKind.Mfd, 4, 2, true, 4, 1, Mfd(0x27)),
|
||||
new PanelGroup("Upper Right MFD", PanelGroupKind.Mfd, 4, 2, true, 8, 1, Mfd(0x37)),
|
||||
|
||||
// Lower MFD row.
|
||||
new PanelGroup("Lower Left MFD", PanelGroupKind.Mfd, 4, 2, true, 0, 5, Mfd(0x0F)),
|
||||
new PanelGroup("Lower Right MFD", PanelGroupKind.Mfd, 4, 2, true, 10, 5, Mfd(0x07)),
|
||||
new PanelGroup("Lower Right MFD", PanelGroupKind.Mfd, 4, 2, true, 8, 5, Mfd(0x07)),
|
||||
|
||||
// Board columns.
|
||||
new PanelGroup("Throttle", PanelGroupKind.Column, 1, 8, true, 0, 9, Col8(0x38)),
|
||||
new PanelGroup("Secondary", PanelGroupKind.Column, 1, 8, true, 5, 9, Col8(0x10)),
|
||||
new PanelGroup("Screen", PanelGroupKind.Column, 1, 8, true, 7, 9, Col8(0x18)),
|
||||
new PanelGroup("Joystick / Hat", PanelGroupKind.Column, 1, 8, true, 13, 9, Col8(0x40)),
|
||||
new PanelGroup("Secondary", PanelGroupKind.Column, 1, 8, true, 4, 9, Col8(0x10)),
|
||||
new PanelGroup("Screen", PanelGroupKind.Column, 1, 8, true, 6, 9, Col8(0x18)),
|
||||
new PanelGroup("Joystick", PanelGroupKind.Column, 1, 8, true, 11, 9, Col8(0x40)),
|
||||
|
||||
// Keypads (no lamps): internal in the middle (below Upper Middle MFD, above
|
||||
// the Secondary/Screen columns); external between those columns and Joystick.
|
||||
new PanelGroup("Internal Keypad", PanelGroupKind.Keypad, 4, 4, false, 5, 4, Keypad(0x50)),
|
||||
new PanelGroup("External Keypad", PanelGroupKind.Keypad, 4, 4, false, 9, 9, Keypad(0x60)),
|
||||
new PanelGroup("Internal Keypad", PanelGroupKind.Keypad, 4, 4, false, 4, 4, Keypad(0x50)),
|
||||
new PanelGroup("External Keypad", PanelGroupKind.Keypad, 4, 4, false, 7, 9, Keypad(0x60)),
|
||||
};
|
||||
|
||||
/// <summary>Flatten the groups to positioned <see cref="PanelButton"/>s (absolute cells).</summary>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace RioJoy.Core.Mapping;
|
||||
|
||||
/// <summary>
|
||||
/// A no-op <see cref="IInputSink"/>: swallows all keyboard/mouse output. Used while
|
||||
/// a profile is being edited so the RIO can be exercised to check button function
|
||||
/// without injecting keystrokes/clicks into the OS (lamp feedback still applies).
|
||||
/// </summary>
|
||||
public sealed class NullInputSink : IInputSink
|
||||
{
|
||||
public void KeyDown(byte virtualKey, bool extended) { }
|
||||
|
||||
public void KeyUp(byte virtualKey, bool extended) { }
|
||||
|
||||
public void MouseMove(int dx, int dy) { }
|
||||
|
||||
public void MouseButton(MouseButton button, bool down) { }
|
||||
}
|
||||
@@ -31,6 +31,13 @@ public sealed class AutoSwitchWatcher
|
||||
/// <summary>The most recently resolved decision (null until the first <see cref="Poll"/>).</summary>
|
||||
public SwitchDecision? Current => _last;
|
||||
|
||||
/// <summary>
|
||||
/// Forget the last decision so the next <see cref="Poll"/> re-raises
|
||||
/// <see cref="DecisionChanged"/> even if the resolved decision is unchanged. Used
|
||||
/// after a manual/editor override to re-sync the coordinator with the foreground app.
|
||||
/// </summary>
|
||||
public void Reset() => _last = null;
|
||||
|
||||
/// <summary>Resolve once; raise <see cref="DecisionChanged"/> if it changed. Returns the decision.</summary>
|
||||
public SwitchDecision Poll()
|
||||
{
|
||||
|
||||
@@ -40,6 +40,13 @@ public sealed class RioRuntime : IRioCommandSink, IDisposable
|
||||
/// <summary>Raised when a diagnostic toggle RIO command fires (raw-axes / poll-rate).</summary>
|
||||
public event Action<RioCommandCode>? DiagnosticToggle;
|
||||
|
||||
/// <summary>
|
||||
/// Raised for every decoded button/keypad event (RIO address, <c>true</c> on press
|
||||
/// and <c>false</c> on release) — independent of how the entry is routed, so the
|
||||
/// editor can show live button activity even with no-op output sinks.
|
||||
/// </summary>
|
||||
public event Action<int, bool>? ButtonActivity;
|
||||
|
||||
/// <summary>Subscribe to the link and set all lamps to their idle state.</summary>
|
||||
public void Start()
|
||||
{
|
||||
@@ -85,20 +92,31 @@ public sealed class RioRuntime : IRioCommandSink, IDisposable
|
||||
switch (packet.Command)
|
||||
{
|
||||
case RioCommand.ButtonPressed when p[0] < RioAddress.ButtonCount:
|
||||
_router.Press(RioAddress.FromButton(p[0]));
|
||||
Activity(RioAddress.FromButton(p[0]), pressed: true);
|
||||
break;
|
||||
case RioCommand.ButtonReleased when p[0] < RioAddress.ButtonCount:
|
||||
_router.Release(RioAddress.FromButton(p[0]));
|
||||
Activity(RioAddress.FromButton(p[0]), pressed: false);
|
||||
break;
|
||||
case RioCommand.KeyPressed when IsKeypad(p):
|
||||
_router.Press(RioAddress.FromKeypad(p[0], p[1]));
|
||||
Activity(RioAddress.FromKeypad(p[0], p[1]), pressed: true);
|
||||
break;
|
||||
case RioCommand.KeyReleased when IsKeypad(p):
|
||||
_router.Release(RioAddress.FromKeypad(p[0], p[1]));
|
||||
Activity(RioAddress.FromKeypad(p[0], p[1]), pressed: false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Route the event through the input pipeline and notify any live-activity listener.
|
||||
private void Activity(int address, bool pressed)
|
||||
{
|
||||
if (pressed)
|
||||
_router.Press(address);
|
||||
else
|
||||
_router.Release(address);
|
||||
|
||||
ButtonActivity?.Invoke(address, pressed);
|
||||
}
|
||||
|
||||
private static bool IsKeypad(ReadOnlySpan<byte> p) => p[0] is 0 or 1 && p[1] <= 0x0F;
|
||||
|
||||
// IRioCommandSink: RIO commands routed from a button (RIOcmd port).
|
||||
|
||||
Reference in New Issue
Block a user