Displays: on-screen cockpit buttons + desktop plasma + the glass preset (steps 2d/2e)

NEW gated TUs: L4PADPANEL (GDI top-level window, all-W APIs: the pod button
banks -- upper/lower aux, 12 secondary, specials/hat/handle, 63 buttons --
clickable via PadRIO::SetScreenButton with lamp-lit faces from GetLampState,
10 Hz repaint; created by the PadRIO ctor on BT_PAD_PANEL=1) and L4PLASMAWIN
(PlasmaWindow : Video8BitBuffered -- the gauge renderer draws the 128x32
plasma into its pixelBuffer through the SAME code path as the serial device;
Update() blits orange-on-black at L4PLASMASCALE, default x4).  Gated seams:
L4GREND.cpp L4PLASMA=SCREEN branch; btl4main.cpp -platform glass preset
(PAD,KEYBOARD + BT_DEV_GAUGES + SCREEN plasma + panel; env always overrides;
non-glass builds log+fall back to DEV); run.cmd glass token.  MFD surfaces
need NO new code: the existing dock-bottom / BT_DEV_GAUGES_WINDOW=1 /
BT_DEV_GAUGES_DOCK=1 modes are the display story.

Verified live: -platform glass boots GLASS profile, panel + plasma windows
up, pad detected, dev gauges awake, mission loop clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-17 22:21:22 -05:00
co-authored by Claude Fable 5
parent 9f35a8034a
commit db9ac947f7
9 changed files with 629 additions and 8 deletions
+167
View File
@@ -0,0 +1,167 @@
#include "mungal4.h"
#pragma hdrstop
//###########################################################################
// L4PLASMAWIN -- the desktop plasma display (BT_GLASS only; this TU is only
// in the build when the gate is on -- see CMakeLists.txt).
// Design: L4PLASMAWIN.h.
//###########################################################################
#include "l4plasmawin.h"
#include <windows.h>
#include <stdlib.h>
#include <string.h>
static LRESULT CALLBACK
PlasmaWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
{
switch (message)
{
case WM_CLOSE:
ShowWindow(window, SW_HIDE); // hide only; the renderer owns it
return 0;
}
return DefWindowProcW(window, message, wparam, lparam);
}
//###########################################################################
PlasmaWindow::PlasmaWindow():
Video8BitBuffered(plasmaWidth, plasmaHeight),
window(NULL),
blitBuffer(NULL)
{
scale = 4;
const char *scale_string = getenv("L4PLASMASCALE");
if (scale_string != NULL)
{
int v = atoi(scale_string);
if (v >= 1 && v <= 16)
{
scale = v;
}
}
blitBuffer = new unsigned long[plasmaWidth * plasmaHeight];
memset(blitBuffer, 0, plasmaWidth * plasmaHeight * sizeof(unsigned long));
DEBUG_STREAM << "[plasmawin] desktop plasma display up (scale x"
<< scale << ")\n" << std::flush;
}
PlasmaWindow::~PlasmaWindow()
{
if (window != NULL)
{
DestroyWindow((HWND)window);
window = NULL;
}
delete[] blitBuffer;
blitBuffer = NULL;
}
void
PlasmaWindow::EnsureWindow()
{
if (window != NULL)
{
return;
}
WNDCLASSW window_class;
memset(&window_class, 0, sizeof(window_class));
window_class.lpfnWndProc = PlasmaWndProc;
window_class.hInstance = GetModuleHandleW(NULL);
window_class.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
window_class.lpszClassName = L"BTPlasmaWnd";
RegisterClassW(&window_class);
RECT frame = { 0, 0, plasmaWidth * scale, plasmaHeight * scale };
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
AdjustWindowRect(&frame, style, FALSE);
window = CreateWindowW(
L"BTPlasmaWnd", L"BattleTech - Plasma",
style,
40, 480,
frame.right - frame.left, frame.bottom - frame.top,
NULL, NULL, GetModuleHandleW(NULL), NULL);
if (window == NULL)
{
DEBUG_STREAM << "[plasmawin] CreateWindow failed\n" << std::flush;
return;
}
ShowWindow((HWND)window, SW_SHOWNOACTIVATE);
}
//
// The per-frame external-display flush (the gauge renderer calls this
// where the serial path streams changed lines out the COM port). Blits
// the whole 8-bit buffer as orange-on-black. Returns False = done (no
// multi-call transfer in progress, unlike the serial path).
//
Logical
PlasmaWindow::Update(Logical force_all)
{
(void)force_all;
EnsureWindow();
if (window == NULL || pixelBuffer == NULL ||
pixelBuffer->Data.MapPointer == NULL)
{
return False;
}
//
// 8-bit index -> BGRA: any lit index renders plasma orange, intensity
// stepped a little by index so shaded glyphs keep their shape. The
// engine's graphics coordinate system puts (0,0) bottom-left; the
// window is top-down, so flip Y in the copy.
//
const Byte *source = pixelBuffer->Data.MapPointer;
for (int y = 0; y < plasmaHeight; ++y)
{
const Byte *line = source + (plasmaHeight - 1 - y) * plasmaWidth;
unsigned long *destination = blitBuffer + y * plasmaWidth;
for (int x = 0; x < plasmaWidth; ++x)
{
Byte index = line[x];
if (index == 0)
{
destination[x] = 0x00180800; // near-black
}
else
{
unsigned long intensity = 160 + (index % 6) * 19; // 160..255
destination[x] =
(intensity << 16) | // R
((intensity * 140 / 255) << 8); // G (amber)
}
}
}
BITMAPINFO info;
memset(&info, 0, sizeof(info));
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
info.bmiHeader.biWidth = plasmaWidth;
info.bmiHeader.biHeight = -plasmaHeight; // top-down
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biBitCount = 32;
info.bmiHeader.biCompression = BI_RGB;
HDC dc = GetDC((HWND)window);
if (dc != NULL)
{
RECT client;
GetClientRect((HWND)window, &client);
StretchDIBits(dc,
0, 0, client.right, client.bottom,
0, 0, plasmaWidth, plasmaHeight,
blitBuffer, &info, DIB_RGB_COLORS, SRCCOPY);
ReleaseDC((HWND)window, dc);
}
return False;
}
void
PlasmaWindow::WaitForUpdate()
{
}