Workstream A prototype: play without the cockpit
Splits the control surface the game consumes from the RIO board into RIOBase (8 virtuals + the five analog scalars); the serial RIO is now one implementation of it. Adds two new ones: - PadRIO (L4CONTROLS=PAD): in-process RIO speaking the full surface from an XInput controller + PC keyboard using vRIO's default profile (left stick/WASD = stick, triggers/Q,E = pedals, right stick Y/PgUp,PgDn = rate throttle that holds position, A/Space = trigger, B/R = reverse, dpad/arrows = hat, Start,Back/F1,F2 = config). Samples in GetNextEvent so button latency does not depend on the 15 s menu-time analog cadence; hot-plugs pads; L4PADFLIP=XY inverts stick axes; lamp commands land in lampState[] for the planned on-screen cockpit panel. The stock VTVRIOMapper/lamp/button path runs unchanged. - PlasmaScreen (L4PLASMA=SCREEN): the 128x32 plasma glass as a desktop window in plasma orange (L4PLASMASCALE, default x4), rendering the same Video8BitBuffered surface the gauge system always drew; no COM port. Verified in the sandbox with vRIO off and no serial devices: boots to a running mission, controller hot-detected, plasma window drawing live game content (score readout). BUILD.md 4 documents the desktop environ.ini and bindings; roadmap updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
#include "mungal4.h"
|
||||
#pragma hdrstop
|
||||
|
||||
#include "l4plasmascreen.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const char plasmaWindowClass[] = "RPPlasmaScreen";
|
||||
|
||||
LRESULT CALLBACK
|
||||
PlasmaWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
PlasmaScreen *screen =
|
||||
(PlasmaScreen *) GetWindowLongPtrA(hwnd, GWLP_USERDATA);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case WM_PAINT:
|
||||
if (screen != NULL)
|
||||
{
|
||||
// handled below through the friend-free public surface:
|
||||
// the window procedure only asks the screen to repaint.
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = BeginPaint(hwnd, &ps);
|
||||
// PlasmaScreen stores what StretchDIBits needs in
|
||||
// window properties to keep the class decoupled.
|
||||
const BITMAPINFO *bmi =
|
||||
(const BITMAPINFO *) GetPropA(hwnd, "PlasmaBitmapInfo");
|
||||
const void *pixels = GetPropA(hwnd, "PlasmaPixels");
|
||||
RECT client;
|
||||
GetClientRect(hwnd, &client);
|
||||
if (bmi != NULL && pixels != NULL)
|
||||
{
|
||||
SetStretchBltMode(hdc, COLORONCOLOR);
|
||||
StretchDIBits(
|
||||
hdc,
|
||||
0, 0, client.right, client.bottom,
|
||||
0, 0, PlasmaScreen::plasmaWidth, PlasmaScreen::plasmaHeight,
|
||||
pixels, bmi, DIB_RGB_COLORS, SRCCOPY
|
||||
);
|
||||
}
|
||||
EndPaint(hwnd, &ps);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_CLOSE:
|
||||
// The glass is part of the cockpit; just hide it.
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
return 0;
|
||||
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
}
|
||||
return DefWindowProcA(hwnd, message, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
//########################################################################
|
||||
//############################ PlasmaScreen ##############################
|
||||
//########################################################################
|
||||
|
||||
PlasmaScreen::PlasmaScreen():
|
||||
Video8BitBuffered(plasmaWidth, plasmaHeight)
|
||||
{
|
||||
Check_Pointer(this);
|
||||
|
||||
window = NULL;
|
||||
bitmapInfo = NULL;
|
||||
|
||||
scale = 4;
|
||||
const char *scale_string = getenv("L4PLASMASCALE");
|
||||
if (scale_string != NULL)
|
||||
{
|
||||
int requested = atoi(scale_string);
|
||||
if (requested >= 1 && requested <= 16)
|
||||
{
|
||||
scale = requested;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Build the 8bpp DIB header with the plasma-orange palette:
|
||||
// index 0 is the dark glass, everything else lights up.
|
||||
//---------------------------------------------------------------
|
||||
BITMAPINFOHEADER *header = (BITMAPINFOHEADER *) new char[
|
||||
sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
|
||||
memset(header, 0, sizeof(BITMAPINFOHEADER));
|
||||
header->biSize = sizeof(BITMAPINFOHEADER);
|
||||
header->biWidth = plasmaWidth;
|
||||
header->biHeight = -plasmaHeight; // top-down, matching PixelMap8
|
||||
header->biPlanes = 1;
|
||||
header->biBitCount = 8;
|
||||
header->biCompression = BI_RGB;
|
||||
header->biClrUsed = 256;
|
||||
|
||||
RGBQUAD *palette = (RGBQUAD *)((char *) header + sizeof(BITMAPINFOHEADER));
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
palette[i].rgbRed = 24; palette[i].rgbGreen = 10; palette[i].rgbBlue = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
palette[i].rgbRed = 255; palette[i].rgbGreen = 144; palette[i].rgbBlue = 32;
|
||||
}
|
||||
palette[i].rgbReserved = 0;
|
||||
}
|
||||
bitmapInfo = header;
|
||||
|
||||
CreateGlassWindow();
|
||||
|
||||
Tell("PlasmaScreen: on-screen plasma display, scale x" << scale << "\n");
|
||||
}
|
||||
|
||||
PlasmaScreen::~PlasmaScreen()
|
||||
{
|
||||
Check_Pointer(this);
|
||||
|
||||
if (window != NULL)
|
||||
{
|
||||
RemovePropA((HWND) window, "PlasmaBitmapInfo");
|
||||
RemovePropA((HWND) window, "PlasmaPixels");
|
||||
DestroyWindow((HWND) window);
|
||||
window = NULL;
|
||||
}
|
||||
delete [] (char *) bitmapInfo;
|
||||
bitmapInfo = NULL;
|
||||
}
|
||||
|
||||
Logical
|
||||
PlasmaScreen::TestInstance() const
|
||||
{
|
||||
return valid;
|
||||
}
|
||||
|
||||
void
|
||||
PlasmaScreen::CreateGlassWindow()
|
||||
{
|
||||
HINSTANCE instance = GetModuleHandleA(NULL);
|
||||
|
||||
static Logical class_registered = False;
|
||||
if (!class_registered)
|
||||
{
|
||||
class_registered = True;
|
||||
|
||||
WNDCLASSA window_class;
|
||||
memset(&window_class, 0, sizeof(window_class));
|
||||
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
||||
window_class.lpfnWndProc = PlasmaWndProc;
|
||||
window_class.hInstance = instance;
|
||||
window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
|
||||
window_class.lpszClassName = plasmaWindowClass;
|
||||
RegisterClassA(&window_class);
|
||||
}
|
||||
|
||||
RECT bounds;
|
||||
bounds.left = 0;
|
||||
bounds.top = 0;
|
||||
bounds.right = plasmaWidth * scale;
|
||||
bounds.bottom = plasmaHeight * scale;
|
||||
AdjustWindowRect(&bounds, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);
|
||||
|
||||
window = CreateWindowExA(
|
||||
0,
|
||||
plasmaWindowClass,
|
||||
"Plasma Display",
|
||||
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
bounds.right - bounds.left,
|
||||
bounds.bottom - bounds.top,
|
||||
NULL, NULL, instance, NULL
|
||||
);
|
||||
|
||||
if (window != NULL)
|
||||
{
|
||||
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
|
||||
SetPropA((HWND) window, "PlasmaBitmapInfo", bitmapInfo);
|
||||
SetPropA((HWND) window, "PlasmaPixels", pixelBuffer->Data.MapPointer);
|
||||
ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_STREAM << "PlasmaScreen: window creation failed\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Called from the gauge renderer's background task, same slot the serial
|
||||
// plasma used for streaming. Repaint when any line changed.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Logical
|
||||
PlasmaScreen::Update(Logical forceall)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
Logical dirty = forceall;
|
||||
|
||||
for (int y = 0; y < plasmaHeight; ++y)
|
||||
{
|
||||
if (changedLine[y])
|
||||
{
|
||||
changedLine[y] = 0;
|
||||
dirty = True;
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty && window != NULL)
|
||||
{
|
||||
InvalidateRect((HWND) window, NULL, FALSE);
|
||||
}
|
||||
|
||||
return False; // 'done' - nothing left to stream
|
||||
}
|
||||
|
||||
void
|
||||
PlasmaScreen::WaitForUpdate()
|
||||
{
|
||||
Check(this);
|
||||
}
|
||||
Reference in New Issue
Block a user