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:
@@ -8,6 +8,9 @@
|
||||
#include "..\munga\mission.h"
|
||||
#include "l4plasma.h"
|
||||
#include "..\munga\notation.h"
|
||||
#ifdef BT_GLASS
|
||||
#include "l4plasmawin.h"
|
||||
#endif
|
||||
|
||||
// #define LOCAL_TEST
|
||||
|
||||
@@ -100,8 +103,23 @@ L4GaugeRenderer::L4GaugeRenderer(bool windowed, int *secondaryIndex, int *aux1In
|
||||
// Tell("Plasma display created on COM1\n");
|
||||
// externalDisplay = new PlasmaDisplay(PCS_COM1);
|
||||
//}
|
||||
#ifdef BT_GLASS
|
||||
//
|
||||
// Glass cockpit (step 2d): L4PLASMA=SCREEN renders the 128x32
|
||||
// plasma into a desktop window instead of a serial device.
|
||||
// Any other value keeps the authentic serial path below.
|
||||
//
|
||||
if (_stricmp(plasma_string, "SCREEN") == 0)
|
||||
{
|
||||
Tell("Plasma display created on desktop window\n");
|
||||
externalDisplay = new PlasmaWindow();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
Tell("Plasma display created on "); Tell(plasma_string); Tell("\n");
|
||||
externalDisplay = new PlasmaDisplay(plasma_string);
|
||||
}
|
||||
|
||||
if (externalDisplay != NULL)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
#include "mungal4.h"
|
||||
#pragma hdrstop
|
||||
|
||||
//###########################################################################
|
||||
// L4PADPANEL -- the on-screen cockpit button panel (BT_GLASS only; this TU
|
||||
// is only in the build when the gate is on -- see CMakeLists.txt).
|
||||
// Design: L4PADPANEL.h.
|
||||
//###########################################################################
|
||||
|
||||
#include "l4padpanel.h"
|
||||
#include "l4padrio.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
//###########################################################################
|
||||
// Layout: rows of banks, each button carries its RIO address + label.
|
||||
//###########################################################################
|
||||
|
||||
struct PanelButton
|
||||
{
|
||||
int address;
|
||||
const char *label;
|
||||
RECT rect; // filled at layout time
|
||||
};
|
||||
|
||||
//
|
||||
// Bank numbering follows the L4CTRL.h enum names: aux banks count DOWN
|
||||
// from 8 at the low address (ButtonAuxLowerRight8=0x00 .. 1=0x07), the
|
||||
// secondary panel counts UP (Secondary1=0x10 .. 6=0x15, 7=0x18 .. 12=0x1D).
|
||||
//
|
||||
static PanelButton panelButtons[] =
|
||||
{
|
||||
// row 0: upper aux left / center / right
|
||||
{0x28,"UL8"},{0x29,"UL7"},{0x2A,"UL6"},{0x2B,"UL5"},
|
||||
{0x2C,"UL4"},{0x2D,"UL3"},{0x2E,"UL2"},{0x2F,"UL1"},
|
||||
{0x20,"UC8"},{0x21,"UC7"},{0x22,"UC6"},{0x23,"UC5"},
|
||||
{0x24,"UC4"},{0x25,"UC3"},{0x26,"UC2"},{0x27,"UC1"},
|
||||
{0x30,"UR8"},{0x31,"UR7"},{0x32,"UR6"},{0x33,"UR5"},
|
||||
{0x34,"UR4"},{0x35,"UR3"},{0x36,"UR2"},{0x37,"UR1"},
|
||||
// row 1: the 12 secondary-panel buttons
|
||||
{0x10,"S1"},{0x11,"S2"},{0x12,"S3"},{0x13,"S4"},{0x14,"S5"},{0x15,"S6"},
|
||||
{0x18,"S7"},{0x19,"S8"},{0x1A,"S9"},{0x1B,"S10"},{0x1C,"S11"},{0x1D,"S12"},
|
||||
// row 2: lower aux left / right
|
||||
{0x08,"LL8"},{0x09,"LL7"},{0x0A,"LL6"},{0x0B,"LL5"},
|
||||
{0x0C,"LL4"},{0x0D,"LL3"},{0x0E,"LL2"},{0x0F,"LL1"},
|
||||
{0x00,"LR8"},{0x01,"LR7"},{0x02,"LR6"},{0x03,"LR5"},
|
||||
{0x04,"LR4"},{0x05,"LR3"},{0x06,"LR2"},{0x07,"LR1"},
|
||||
// row 3: specials + the stick/throttle handle
|
||||
{0x3C,"DOOR"},{0x3D,"PANIC"},{0x3F,"REV"},{0x40,"TRIG"},
|
||||
{0x41,"HAT-D"},{0x42,"HAT-U"},{0x43,"HAT-R"},{0x44,"HAT-L"},
|
||||
{0x45,"PNKY"},{0x46,"THB-"},{0x47,"THB+"},
|
||||
};
|
||||
enum { PanelButtonCount = sizeof(panelButtons)/sizeof(panelButtons[0]) };
|
||||
|
||||
//
|
||||
// Row boundaries (index into panelButtons) + per-row bank gaps (an extra
|
||||
// horizontal gap after these column indices, visually separating banks).
|
||||
//
|
||||
struct PanelRow
|
||||
{
|
||||
int first, count;
|
||||
int gapAfterA, gapAfterB; // -1 = none
|
||||
};
|
||||
static const PanelRow panelRows[] =
|
||||
{
|
||||
{ 0, 24, 7, 15 }, // UL | UC | UR
|
||||
{ 24, 12, 5, -1 }, // S1-6 | S7-12
|
||||
{ 36, 16, 7, -1 }, // LL | LR
|
||||
{ 52, 11, 3, 7 }, // specials | hat | thumbs
|
||||
};
|
||||
enum { PanelRowCount = sizeof(panelRows)/sizeof(panelRows[0]) };
|
||||
|
||||
enum
|
||||
{
|
||||
ButtonWidth = 42,
|
||||
ButtonHeight = 30,
|
||||
ButtonGap = 4,
|
||||
BankGap = 14,
|
||||
PanelMargin = 10,
|
||||
RepaintTimerId = 1,
|
||||
RepaintMilliseconds = 100
|
||||
};
|
||||
|
||||
static HWND panelWindow = NULL;
|
||||
static int pressedAddress = -1;
|
||||
|
||||
//###########################################################################
|
||||
|
||||
static void
|
||||
LayoutButtons()
|
||||
{
|
||||
int y = PanelMargin;
|
||||
for (int r = 0; r < PanelRowCount; ++r)
|
||||
{
|
||||
int x = PanelMargin;
|
||||
for (int c = 0; c < panelRows[r].count; ++c)
|
||||
{
|
||||
PanelButton &button = panelButtons[panelRows[r].first + c];
|
||||
button.rect.left = x;
|
||||
button.rect.top = y;
|
||||
button.rect.right = x + ButtonWidth;
|
||||
button.rect.bottom = y + ButtonHeight;
|
||||
x += ButtonWidth + ButtonGap;
|
||||
if (c == panelRows[r].gapAfterA || c == panelRows[r].gapAfterB)
|
||||
{
|
||||
x += BankGap;
|
||||
}
|
||||
}
|
||||
y += ButtonHeight + ButtonGap + 4;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
HitTest(int x, int y)
|
||||
{
|
||||
for (int i = 0; i < PanelButtonCount; ++i)
|
||||
{
|
||||
const RECT &r = panelButtons[i].rect;
|
||||
if (x >= r.left && x < r.right && y >= r.top && y < r.bottom)
|
||||
{
|
||||
return panelButtons[i].address;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//
|
||||
// Lamp color from the recorded RIO lamp state: the two filament fields
|
||||
// (state1 0x04 dim / 0x0C bright, state2 0x10 dim / 0x30 bright) collapse
|
||||
// to a single dev-panel brightness; flash modes render as lit (the pod's
|
||||
// flash timing is not simulated on the panel).
|
||||
//
|
||||
static COLORREF
|
||||
LampColor(int state, int pressed)
|
||||
{
|
||||
int level1 = (state >> 2) & 0x3; // 0/1/3
|
||||
int level2 = (state >> 4) & 0x3;
|
||||
int level = (level1 > level2) ? level1 : level2;
|
||||
if (pressed)
|
||||
{
|
||||
return RGB(255, 255, 160);
|
||||
}
|
||||
switch (level)
|
||||
{
|
||||
case 0: return RGB(52, 56, 52); // off: dead face
|
||||
case 1: return RGB(64, 120, 64); // dim
|
||||
default: return RGB(80, 220, 80); // bright
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
PaintPanel(HWND window)
|
||||
{
|
||||
PAINTSTRUCT paint;
|
||||
HDC dc = BeginPaint(window, &paint);
|
||||
|
||||
RECT client;
|
||||
GetClientRect(window, &client);
|
||||
HBRUSH background = CreateSolidBrush(RGB(24, 26, 28));
|
||||
FillRect(dc, &client, background);
|
||||
DeleteObject(background);
|
||||
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
HFONT font = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
|
||||
HFONT old_font = (HFONT)SelectObject(dc, font);
|
||||
|
||||
for (int i = 0; i < PanelButtonCount; ++i)
|
||||
{
|
||||
const PanelButton &button = panelButtons[i];
|
||||
int lamp = PadRIO::GetLampState(button.address);
|
||||
int pressed = (button.address == pressedAddress);
|
||||
|
||||
HBRUSH face = CreateSolidBrush(LampColor(lamp, pressed));
|
||||
FillRect(dc, &button.rect, face);
|
||||
DeleteObject(face);
|
||||
FrameRect(dc, &button.rect,
|
||||
(HBRUSH)GetStockObject(pressed ? WHITE_BRUSH : GRAY_BRUSH));
|
||||
|
||||
SetTextColor(dc, RGB(230, 230, 230));
|
||||
WCHAR label[16];
|
||||
int n = 0;
|
||||
for (const char *s = button.label; *s && n < 15; ++s)
|
||||
{
|
||||
label[n++] = (WCHAR)*s;
|
||||
}
|
||||
label[n] = 0;
|
||||
RECT text_rect = button.rect;
|
||||
DrawTextW(dc, label, -1, &text_rect,
|
||||
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
|
||||
SelectObject(dc, old_font);
|
||||
EndPaint(window, &paint);
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK
|
||||
PanelWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case WM_PAINT:
|
||||
PaintPanel(window);
|
||||
return 0;
|
||||
|
||||
case WM_TIMER:
|
||||
if (wparam == RepaintTimerId)
|
||||
{
|
||||
InvalidateRect(window, NULL, FALSE);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_LBUTTONDOWN:
|
||||
{
|
||||
int address = HitTest((int)(short)LOWORD(lparam),
|
||||
(int)(short)HIWORD(lparam));
|
||||
if (address >= 0)
|
||||
{
|
||||
pressedAddress = address;
|
||||
SetCapture(window);
|
||||
PadRIO::SetScreenButton(address, 1);
|
||||
InvalidateRect(window, NULL, FALSE);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_LBUTTONUP:
|
||||
if (pressedAddress >= 0)
|
||||
{
|
||||
PadRIO::SetScreenButton(pressedAddress, 0);
|
||||
pressedAddress = -1;
|
||||
ReleaseCapture();
|
||||
InvalidateRect(window, NULL, FALSE);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_CLOSE:
|
||||
//
|
||||
// Closing the panel just hides it -- the device stays up; the
|
||||
// window returns on the next run. (No DestroyWindow: the game
|
||||
// owns the lifetime through BTPadPanel_Destroy.)
|
||||
//
|
||||
ShowWindow(window, SW_HIDE);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProcW(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
|
||||
void
|
||||
BTPadPanel_Create()
|
||||
{
|
||||
if (panelWindow != NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LayoutButtons();
|
||||
|
||||
//
|
||||
// Window size from the widest row (row 0: 24 buttons + 2 bank gaps).
|
||||
//
|
||||
int client_width = 0;
|
||||
for (int i = 0; i < PanelButtonCount; ++i)
|
||||
{
|
||||
if (panelButtons[i].rect.right > client_width)
|
||||
{
|
||||
client_width = panelButtons[i].rect.right;
|
||||
}
|
||||
}
|
||||
client_width += PanelMargin;
|
||||
int client_height = panelButtons[PanelButtonCount-1].rect.bottom
|
||||
+ PanelMargin;
|
||||
|
||||
WNDCLASSW window_class;
|
||||
memset(&window_class, 0, sizeof(window_class));
|
||||
window_class.lpfnWndProc = PanelWndProc;
|
||||
window_class.hInstance = GetModuleHandleW(NULL);
|
||||
window_class.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
|
||||
window_class.lpszClassName = L"BTPadPanelWnd";
|
||||
RegisterClassW(&window_class);
|
||||
|
||||
RECT frame = { 0, 0, client_width, client_height };
|
||||
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
|
||||
AdjustWindowRect(&frame, style, FALSE);
|
||||
|
||||
panelWindow = CreateWindowW(
|
||||
L"BTPadPanelWnd", L"BattleTech - Cockpit Buttons",
|
||||
style,
|
||||
40, 40,
|
||||
frame.right - frame.left, frame.bottom - frame.top,
|
||||
NULL, NULL, GetModuleHandleW(NULL), NULL);
|
||||
if (panelWindow == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "[padpanel] CreateWindow failed\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
SetTimer(panelWindow, RepaintTimerId, RepaintMilliseconds, NULL);
|
||||
ShowWindow(panelWindow, SW_SHOWNOACTIVATE);
|
||||
DEBUG_STREAM << "[padpanel] cockpit button panel up ("
|
||||
<< PanelButtonCount << " buttons)\n" << std::flush;
|
||||
}
|
||||
|
||||
void
|
||||
BTPadPanel_Destroy()
|
||||
{
|
||||
if (panelWindow != NULL)
|
||||
{
|
||||
KillTimer(panelWindow, RepaintTimerId);
|
||||
DestroyWindow(panelWindow);
|
||||
panelWindow = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
//###########################################################################
|
||||
//
|
||||
// L4PADPANEL -- the on-screen cockpit button panel (BT_GLASS only).
|
||||
//
|
||||
// A developer-grade GDI window presenting the pod's RIO button banks as
|
||||
// clickable, lamp-lit buttons: upper aux banks (0x20-0x37), the 12
|
||||
// secondary-panel buttons (0x10-0x15, 0x18-0x1D), lower aux banks
|
||||
// (0x00-0x0F), and the special/handle row (door, panic, reverse, trigger,
|
||||
// hat, pinky/thumbs). Lamps render from PadRIO::GetLampState; clicks
|
||||
// inject through PadRIO::SetScreenButton. Created by the PadRIO ctor when
|
||||
// BT_PAD_PANEL=1 (the glass preset sets it); destroyed with the PadRIO.
|
||||
//
|
||||
// All window-management uses the W APIs (the L4VB16 mojibake lesson). The
|
||||
// window lives on the app thread and repaints on a ~10 Hz timer.
|
||||
//
|
||||
//###########################################################################
|
||||
|
||||
void
|
||||
BTPadPanel_Create();
|
||||
void
|
||||
BTPadPanel_Destroy();
|
||||
@@ -8,6 +8,7 @@
|
||||
//###########################################################################
|
||||
|
||||
#include "l4padrio.h"
|
||||
#include "l4padpanel.h"
|
||||
#include "l4ctrl.h"
|
||||
|
||||
#include <windows.h>
|
||||
@@ -121,10 +122,20 @@ PadRIO::PadRIO():
|
||||
activeInstance = this;
|
||||
DEBUG_STREAM << "[padrio] PadRIO up (XInput probe pending; keyboard "
|
||||
<< "live on focus; L4PADFLIP=" << flipStickAxes << ")\n" << std::flush;
|
||||
|
||||
//
|
||||
// The on-screen cockpit button panel (step 2d) rides the device: the
|
||||
// glass preset sets BT_PAD_PANEL=1.
|
||||
//
|
||||
if (getenv("BT_PAD_PANEL") != NULL && *getenv("BT_PAD_PANEL") != '0')
|
||||
{
|
||||
BTPadPanel_Create();
|
||||
}
|
||||
}
|
||||
|
||||
PadRIO::~PadRIO()
|
||||
{
|
||||
BTPadPanel_Destroy();
|
||||
if (activeInstance == this)
|
||||
{
|
||||
activeInstance = NULL;
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
//###########################################################################
|
||||
//
|
||||
// L4PLASMAWIN -- the desktop plasma display (BT_GLASS only).
|
||||
//
|
||||
// PlasmaWindow renders the pod's 128x32 serial plasma marquee (pilot-name /
|
||||
// rank display) into a small orange-on-black desktop window instead of a
|
||||
// serial device. It derives from Video8BitBuffered exactly like the serial
|
||||
// PlasmaDisplay, so the L4GaugeRenderer draws into its 8-bit pixelBuffer
|
||||
// with the same code path; Update() (the per-frame external-display flush)
|
||||
// blits the buffer to the window at L4PLASMASCALE x zoom (default 4).
|
||||
// Selected with L4PLASMA=SCREEN (the gated branch in L4GREND.cpp); any
|
||||
// other L4PLASMA value keeps the serial device.
|
||||
//
|
||||
//###########################################################################
|
||||
|
||||
#include "l4vb8.h"
|
||||
|
||||
class PlasmaWindow :
|
||||
public Video8BitBuffered
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
plasmaWidth = 128,
|
||||
plasmaHeight = 32
|
||||
};
|
||||
|
||||
PlasmaWindow();
|
||||
~PlasmaWindow();
|
||||
|
||||
Logical
|
||||
Update(Logical force_all);
|
||||
|
||||
void
|
||||
WaitForUpdate();
|
||||
|
||||
protected:
|
||||
void
|
||||
EnsureWindow();
|
||||
|
||||
void
|
||||
*window; // HWND (kept void* -- no <windows.h> in headers)
|
||||
int
|
||||
scale;
|
||||
unsigned long
|
||||
*blitBuffer; // plasmaWidth*plasmaHeight BGRA staging
|
||||
};
|
||||
Reference in New Issue
Block a user