The desktop plasma window (L4PLASMAWIN, "BattleTech - Plasma") is created by a
different TU than the per-display panels, so it couldn't be a gWins[] entry and
sat outside the sticky-layout / ,noframe system. Add a tiny extern-window
registry to L4GLASSWIN so it rides the same glass_layout.cfg:
- BTGlassLayout_QueryWindow(title, rect, noframe) -- read a title's saved rect
and ,noframe flag (used by the plasma on creation, BEFORE sizing, since
WS_POPUP's frame extent differs from the framed tool window).
- BTGlassLayout_RegisterExtern(hwnd, title) -- register an externally-created
window so SaveLayout writes its line (with a last-known-rect cache so a
teardown before the save still preserves the line).
- BTGlassLayout_Save() -- public save trigger the plasma WndProc calls on
WM_EXITSIZEMOVE / teardown.
L4PLASMAWIN now reads its saved rect+flag on create (WS_POPUP when ,noframe,
position restored), registers itself, and saves on finished-drag/teardown. All
BT_GLASS-gated; the pod is untouched. The plasma blits directly every frame, so
it has no WM_TIMER focus-throttle.
Verified: Release links clean; drag wrote "BattleTech - Plasma=321,222,528,167",
reload with ,noframe brought it up WS_POPUP (WS_CAPTION absent) at 321,222.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
211 lines
6.0 KiB
C++
211 lines
6.0 KiB
C++
#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_EXITSIZEMOVE:
|
|
// Finished dragging (framed mode): persist the new position so it sticks
|
|
// -- same contract as the per-display panels. No-op unless save mode.
|
|
{ extern void BTGlassLayout_Save(); BTGlassLayout_Save(); }
|
|
return 0;
|
|
|
|
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)
|
|
{
|
|
{ extern void BTGlassLayout_Save(); BTGlassLayout_Save(); } // backstop before the HWND goes
|
|
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);
|
|
|
|
// glass_layout.cfg integration (2026-08-04): the plasma window rides the same
|
|
// file as the per-display panels, so it can be dragged-and-remembered and set
|
|
// frameless with ",noframe" -- BT_GLASS_LAYOUT selects load/save. Read its
|
|
// saved rect + flag BEFORE sizing (WS_POPUP has a different frame extent than
|
|
// the framed tool window).
|
|
extern int BTGlassLayout_QueryWindow(const char *title, RECT *rect, int *noframe);
|
|
extern void BTGlassLayout_RegisterExtern(HWND hwnd, const char *title);
|
|
RECT saved;
|
|
int noframe = 0;
|
|
int have_saved = BTGlassLayout_QueryWindow("BattleTech - Plasma", &saved, &noframe);
|
|
|
|
DWORD style = noframe
|
|
? (DWORD)WS_POPUP // bare + pinned, same as a ",noframe" glass panel
|
|
: (DWORD)(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
|
|
|
|
RECT frame = { 0, 0, plasmaWidth * scale, plasmaHeight * scale };
|
|
AdjustWindowRect(&frame, style, FALSE);
|
|
int frame_width = frame.right - frame.left;
|
|
int frame_height = frame.bottom - frame.top;
|
|
|
|
int plasma_x, plasma_y;
|
|
if (have_saved)
|
|
{
|
|
plasma_x = saved.left; // position restored; size stays native
|
|
plasma_y = saved.top;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Default: bottom-right, TOPMOST -- under the (topmost, right-parked)
|
|
// button panel, clear of the game window (which would otherwise bury it).
|
|
//
|
|
plasma_x = GetSystemMetrics(SM_CXSCREEN) - frame_width - 12;
|
|
plasma_y = GetSystemMetrics(SM_CYSCREEN) - frame_height - 60;
|
|
if (plasma_x < 0) plasma_x = 0;
|
|
if (plasma_y < 0) plasma_y = 0;
|
|
}
|
|
|
|
window = CreateWindowExW(
|
|
WS_EX_TOPMOST,
|
|
L"BTPlasmaWnd", L"BattleTech - Plasma",
|
|
style,
|
|
plasma_x, plasma_y,
|
|
frame_width, frame_height,
|
|
NULL, NULL, GetModuleHandleW(NULL), NULL);
|
|
if (window == NULL)
|
|
{
|
|
DEBUG_STREAM << "[plasmawin] CreateWindow failed\n" << std::flush;
|
|
return;
|
|
}
|
|
BTGlassLayout_RegisterExtern((HWND)window, "BattleTech - Plasma");
|
|
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
|
|
// gauge renderer writes the buffer TOP-DOWN (user-verified 2026-07-17:
|
|
// a bottom-up flip rendered the marquee upside down) -- copy straight.
|
|
//
|
|
const Byte *source = pixelBuffer->Data.MapPointer;
|
|
for (int y = 0; y < plasmaHeight; ++y)
|
|
{
|
|
const Byte *line = source + 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()
|
|
{
|
|
}
|