The main game window becomes the cockpit shell (enlarged, clipping children); every display folds in as a chrome-less child pane in the pod interior arrangement: [ MFD UL ] [ MFD UC ] [ MFD UR ] [ plasma (reduced) ][ viewscreen (centered) ] [ MFD LL ] [ Map ] [ MFD LR ] The 3D scene presents into a black STATIC viewscreen child via Present's hDestWindowOverride (new gMainPresentWindow global) - no swap-chain changes, and STATIC's transparent hit-testing keeps mouse input over the 3D view flowing to the game window. MFDSplitView gains a parent/child mode; PlasmaScreen::Position reparents the glass into the shell. Main window class background goes black for the cockpit gaps. Verified by screenshot: live green gauges (LIFT CUT / BOOST / CHUTE / trigger-program screens) with their red button strips, the 3D canyon in the centered viewscreen, plasma score glass at its left, map with lit amber preset lamps - one window, 976x1132 client at 50% scale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
309 lines
7.4 KiB
C++
309 lines
7.4 KiB
C++
#include "mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "l4plasmascreen.h"
|
|
#include "l4app.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::activeInstance = NULL;
|
|
|
|
void
|
|
PlasmaScreen::Position(void *parent, int x, int y, int client_width, int client_height)
|
|
{
|
|
if (activeInstance == NULL || activeInstance->window == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
HWND glass = (HWND) activeInstance->window;
|
|
int window_w = client_width;
|
|
int window_h = client_height;
|
|
|
|
if (parent != NULL)
|
|
{
|
|
// fold the glass into the cockpit window as a chrome-less pane
|
|
SetWindowLongPtrA(glass, GWL_STYLE, WS_CHILD | WS_VISIBLE);
|
|
SetParent(glass, (HWND) parent);
|
|
}
|
|
else
|
|
{
|
|
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
|
|
RECT bounds;
|
|
bounds.left = 0;
|
|
bounds.top = 0;
|
|
bounds.right = client_width;
|
|
bounds.bottom = client_height;
|
|
AdjustWindowRect(&bounds, style, FALSE);
|
|
window_w = bounds.right - bounds.left;
|
|
window_h = bounds.bottom - bounds.top;
|
|
}
|
|
|
|
SetWindowPos(
|
|
glass, NULL,
|
|
x, y,
|
|
window_w,
|
|
window_h,
|
|
SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW
|
|
);
|
|
}
|
|
|
|
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 (activeInstance == this)
|
|
{
|
|
activeInstance = NULL;
|
|
}
|
|
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);
|
|
|
|
int window_w = bounds.right - bounds.left;
|
|
int window_h = bounds.bottom - bounds.top;
|
|
|
|
//---------------------------------------------------------------
|
|
// Default position: directly below the main view (the pod mounts
|
|
// the glass adjacent to the displays). The main window sits at
|
|
// (0,0) with the -res size; clamp to the work area in case the
|
|
// main view fills the screen. L4PLASMAPOS=x,y overrides.
|
|
//---------------------------------------------------------------
|
|
int x = 0;
|
|
int y = (int) L4Application::GetScreenHeight();
|
|
|
|
const char *pos_string = getenv("L4PLASMAPOS");
|
|
if (pos_string != NULL)
|
|
{
|
|
int px = 0, py = 0;
|
|
if (sscanf(pos_string, "%d,%d", &px, &py) == 2)
|
|
{
|
|
x = px;
|
|
y = py;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RECT work;
|
|
if (SystemParametersInfoA(SPI_GETWORKAREA, 0, &work, 0))
|
|
{
|
|
if (y + window_h > work.bottom)
|
|
{
|
|
y = work.bottom - window_h;
|
|
}
|
|
if (y < 0)
|
|
{
|
|
y = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
window = CreateWindowExA(
|
|
0,
|
|
plasmaWindowClass,
|
|
"Plasma Display",
|
|
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
|
|
x, y,
|
|
window_w,
|
|
window_h,
|
|
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);
|
|
activeInstance = this;
|
|
}
|
|
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);
|
|
}
|