Replicates the RP412 cockpit line into BT411's own architecture -- porting the geometry and keeping our renderers, so BT_SHOT single-frame verification stays intact. MODE RESOLVER. Where the secondary displays go was decided in TWO places with duplicated precedence, and the boot banner read NEITHER -- it announced "per-display cockpit windows" for every glass boot, surround included. The split had also broken BT_COCKPIT=0: documented as the dock-bottom opt-out, the profile block converted it to BT_GLASS_PANELS=1, so the docked strip was UNREACHABLE under the glass profile. One resolver now, consumed by the banner, the pad-panel decision and the window sizing; BT_GLASS_PANELS is explicit-only; dock/window modes auto-raise BT_PAD_PANEL so the button field always has a home. L4RIOBANK -- one button geometry. Both renderers carried their own copy and had drifted: an MFD button was 156x138 reaching under the glass in the exploded window and a 76x24 sliver entirely OUTSIDE the glass in the surround. One module owns it now, both are consumers, placement stays per-renderer. The pod's under-glass rule (RP412 L4MFDVIEW): reach half the glass in behind the display, leave a lamp strip clearing the edge, paint buttons first and imagery over -- so the lamp reads as a bar and practically the whole display is the press target. The strip scales off the display's SHORT axis (the map is portrait) with a readable floor. Retired L4GLASSWIN's three local placers and seven layout constants. LAMP FLASH DECODE was wrong [T1]. BTLampBrightnessOf returned max(state1, state2) and blanked on the alternate phase; RIO::LampState (L4RIO.h [T0]) says solid shows state 1 and flashing ALTERNATES the two. Agrees only when one state is Off -- true for the Panic lamp, which is why it survived -- but L4LAMP.cpp:252 commands flashFast + state1Dim + state2Bright, a dim->bright pulse that rendered as a hard bright->off blink. Three copies existed (l4vb16.h, L4GLASSWIN, L4PADPANEL), all three wrong; the two locals now forward to the one fixed inline. THE COCKPIT SCALES. The fixed canvas was stretched into whatever the client area was, so a window dragged to a different shape squashed the instruments (the projection was aspect-corrected in task #20; the panels never were). Now one uniform scale, centred, leftover black. D3D9 applies it as a Present destination rect, which DISCARD forbids -- so the WINDOWED swap effect becomes COPY when the surround is up and multisampling is off. The click mapping had to follow (mapping against the full client drifts the hit test off every button by the bar width), as did the world aspect (under a uniform scale it is the view rect's own). -fit / -windowed-fullscreen: borderless over the monitor. - ordering trap: the first WM_SIZE beats the device, so a -fit boot logged aspect=3.14 and applied it on frame 1. The letterbox INTENT is decided in btl4main; L4VIDEO only confirms or withdraws it. PLAYER-TUNABLE DISPLAYS. BT_MFD_SCALE (+ _UL/_UC/_UR/_LL/_LR), BT_RADAR_SCALE, BT_RADAR_POS (CENTER/LEFT/RIGHT/MIDLEFT/MIDRIGHT). The surround BANDS derive from the resolved sizes -- that is why the sizes could not stay constants: the band a display hangs in has to grow with it or the canvas clips it. 100% reproduces the historical L276 R276 T223 B336 exactly. A corner map goes flush to the CANVAS edge and the lower MFD slides beside it (measuring off the view edge overlapped them by 232px). MAP LEGEND GRID -- measured, not inherited. scratchpad/measurelegend.py over a native capture: top 3, cell 102, pitch 107 of 640. RP412's map is 13 + 6x102 @ 105 -- same cell height, different top and pitch, so its numbers do NOT transfer. Our old even division had the pitch right by luck and sat 3px high of the labels. environ.ini. It was read ~300 lines into WinMain, AFTER the platform-profile block had run its getenv()s -- so every setting the profile reads was silently ignored FROM THE FILE and only worked as a real env var. It also putenv()'d comments verbatim. Now loaded immediately after the first-breath line, comments skipped, the real environment WINS over the file, and a fully documented default is written on first run (the bindings.txt convention: untracked, so extract-over-top never clobbers a player's settings). VERIFICATION HARNESS (new, reusable): BT_RIOBANK_LOG=1 dumps every bank; checkbank.py proves no address is SHADOWED (an address whose rect is covered by earlier buttons is dead however big it looks -- the overlapping under-glass banks make that a live hazard); clickbank.py posts a real click at every button centre. Verified: 72/72 placed, 0 shadowed, 72/72 dispatched in BOTH modes, after a resize, at 150%/135%, and at 75%+BOTTOMRIGHT; wide/tall drags and -fit undistorted on a 3440x1440; exploded/dock/pod/dev un-regressed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
579 lines
17 KiB
C++
579 lines
17 KiB
C++
#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).
|
|
//
|
|
// Layout + coloration follow vRIO (C:\VWE\vrio, src/VRio.Core/Panel/
|
|
// CockpitLayout.cs + src/VRio.App/PanelCanvas.cs) -- the software replica
|
|
// of the RIO board whose panel mirrors RIOJoy's profile editor (the
|
|
// original Win32 RIO control-panel mockup): five 4x2 MFD clusters, four
|
|
// vertical 1x8 board columns, two 4x4 hex keypads; red lamp cells (white
|
|
// text), yellow Secondary/Screen columns (black text), neutral-blue
|
|
// lampless keypads, lamp flash at 500/250/125 ms half-periods. Keeping
|
|
// the same layout means a button here sits where it sits in vRIO/RIOJoy.
|
|
//
|
|
// Left-click = momentary press. Right-click = latch toggle. Keypad cells
|
|
// (addresses 0x50-0x6F) emit keypad key events; everything else emits RIO
|
|
// button events -- both via PadRIO::SetScreenButton.
|
|
//###########################################################################
|
|
|
|
#include "l4padpanel.h"
|
|
#include "l4padrio.h"
|
|
#include "l4vb16.h" // BTLampBrightnessOf -- the one lamp decode
|
|
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
//###########################################################################
|
|
// The vRIO cockpit layout (CockpitLayout.cs transcribed)
|
|
//###########################################################################
|
|
|
|
enum
|
|
{
|
|
CellW = 66,
|
|
CellH = 34,
|
|
PanelMargin = 6,
|
|
FirstRow = 1, // the shared layout leaves grid row 0 empty
|
|
RepaintTimerId = 1,
|
|
RepaintMilliseconds = 62 // issue #13: ~half the fastest lamp-flash half-period
|
|
// (125ms) -- the old 100ms SAMPLED the 125/250/500ms
|
|
// flash rhythms into an irregular "broken lamp" beat
|
|
};
|
|
|
|
enum GroupKind { KindMfd, KindColumn, KindKeypad };
|
|
|
|
struct PanelGroupDef
|
|
{
|
|
const char *title;
|
|
GroupKind kind;
|
|
int cols, rows;
|
|
int lampCapable;
|
|
int originCol, originRow;
|
|
int baseAddress; // Mfd: hi address; Column: base; Keypad: base
|
|
};
|
|
|
|
//
|
|
// Groups exactly as CockpitLayout.Groups: MFD blocks list top row then
|
|
// bottom row with addresses DESCENDING from the hi address; columns run
|
|
// base..base+7 top to bottom; keypads place hex keys 1 2 3 C / 4 5 6 D /
|
|
// 7 8 9 E / A 0 B F at base+digit.
|
|
//
|
|
static const PanelGroupDef panelGroups[] =
|
|
{
|
|
{ "Upper Left MFD", KindMfd, 4, 2, 1, 0, 1, 0x2F },
|
|
{ "Upper Middle MFD", KindMfd, 4, 2, 1, 4, 1, 0x27 },
|
|
{ "Upper Right MFD", KindMfd, 4, 2, 1, 8, 1, 0x37 },
|
|
{ "Lower Left MFD", KindMfd, 4, 2, 1, 0, 5, 0x0F },
|
|
{ "Lower Right MFD", KindMfd, 4, 2, 1, 8, 5, 0x07 },
|
|
// The four board columns sit CENTERED between the lower MFD stacks,
|
|
// raised into the space the internal keypad vacated (per Cyd
|
|
// 2026-07-18: condensed panel, no keypads). Short titles fit the
|
|
// one-cell frames.
|
|
{ "Thr", KindColumn, 1, 8, 1, 4, 4, 0x38 },
|
|
{ "Sec", KindColumn, 1, 8, 1, 5, 4, 0x10 },
|
|
{ "Scr", KindColumn, 1, 8, 1, 6, 4, 0x18 },
|
|
{ "Joy", KindColumn, 1, 8, 1, 7, 4, 0x40 },
|
|
// (The two 4x4 hex keypads -- Internal 0x50 / External 0x60 -- were
|
|
// dropped from the glass panel per Cyd 2026-07-18; the engine keypad
|
|
// units remain reachable through bindings.txt `keypad` rows.)
|
|
};
|
|
enum { PanelGroupCount = sizeof(panelGroups)/sizeof(panelGroups[0]) };
|
|
|
|
static const int keypadDigits[16] =
|
|
{ 0x1, 0x2, 0x3, 0xC, 0x4, 0x5, 0x6, 0xD,
|
|
0x7, 0x8, 0x9, 0xE, 0xA, 0x0, 0xB, 0xF };
|
|
|
|
struct NamedButton
|
|
{
|
|
int address;
|
|
const char *name;
|
|
};
|
|
static const NamedButton namedButtons[] =
|
|
{
|
|
{ 0x3D, "Panic" },
|
|
{ 0x3F, "Throttle" },
|
|
{ 0x40, "Main" },
|
|
{ 0x41, "Hat Back" },
|
|
{ 0x42, "Hat Up" },
|
|
{ 0x43, "Hat Right" },
|
|
{ 0x44, "Hat Left" },
|
|
{ 0x45, "Pinky" },
|
|
{ 0x46, "Middle" },
|
|
{ 0x47, "Upper" },
|
|
};
|
|
enum { NamedButtonCount = sizeof(namedButtons)/sizeof(namedButtons[0]) };
|
|
|
|
struct PanelCell
|
|
{
|
|
int address;
|
|
const PanelGroupDef *group;
|
|
RECT rect;
|
|
};
|
|
|
|
static PanelCell panelCells[128];
|
|
static int panelCellCount = 0;
|
|
|
|
static HWND panelWindow = NULL;
|
|
static int pressedAddress = -1;
|
|
static unsigned char latched[128];
|
|
|
|
static HFONT titleFont = NULL; // group titles (bold)
|
|
static HFONT buttonFont = NULL; // cell labels
|
|
static HFONT subFont = NULL; // the small hex under a name
|
|
|
|
//###########################################################################
|
|
|
|
static RECT
|
|
CellRect(int col, int row)
|
|
{
|
|
RECT r;
|
|
r.left = col * CellW + 2;
|
|
r.top = PanelMargin + (row - FirstRow) * CellH + 2;
|
|
r.right = r.left + CellW - 4;
|
|
r.bottom = r.top + CellH - 4;
|
|
return r;
|
|
}
|
|
|
|
static void
|
|
LayoutCells()
|
|
{
|
|
panelCellCount = 0;
|
|
for (int g = 0; g < PanelGroupCount; ++g)
|
|
{
|
|
const PanelGroupDef &group = panelGroups[g];
|
|
int count = group.cols * group.rows;
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
int address;
|
|
switch (group.kind)
|
|
{
|
|
case KindMfd: address = group.baseAddress - i; break;
|
|
case KindColumn: address = group.baseAddress + i; break;
|
|
default: address = group.baseAddress + keypadDigits[i]; break;
|
|
}
|
|
PanelCell &cell = panelCells[panelCellCount++];
|
|
cell.address = address;
|
|
cell.group = &group;
|
|
// +1 row leaves space under the group title.
|
|
cell.rect = CellRect(group.originCol + i % group.cols,
|
|
group.originRow + 1 + i / group.cols);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
PhysicalName(int address)
|
|
{
|
|
for (int i = 0; i < NamedButtonCount; ++i)
|
|
{
|
|
if (namedButtons[i].address == address)
|
|
{
|
|
return namedButtons[i].name;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
HitTest(int x, int y)
|
|
{
|
|
for (int i = 0; i < panelCellCount; ++i)
|
|
{
|
|
const RECT &r = panelCells[i].rect;
|
|
if (x >= r.left && x < r.right && y >= r.top && y < r.bottom)
|
|
{
|
|
return panelCells[i].address;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//###########################################################################
|
|
// Lamp decode (RIOBase::LampState): the ONE copy lives in l4vb16.h
|
|
// (BTLampBrightnessOf) -- flash mode in the low 2 bits picks a 500/250/125 ms
|
|
// half-period and the two 2-bit brightness fields (state1 >>2, state2 >>4;
|
|
// 0 off / 1 dim / 3 bright) ALTERNATE across it. This TU used to carry its
|
|
// own max-then-blank copy, as did L4GLASSWIN; see the header's 2026-07-26
|
|
// note for the dim-to-bright pulse all three got wrong.
|
|
//###########################################################################
|
|
|
|
static int
|
|
LampBrightnessOf(int state, unsigned long tick)
|
|
{
|
|
return BTLampBrightnessOf(state, tick);
|
|
}
|
|
|
|
//###########################################################################
|
|
// Painting (PanelCanvas.cs coloration)
|
|
//###########################################################################
|
|
|
|
static void
|
|
DrawLabelText(HDC dc, const char *text, HFONT font, RECT *rect,
|
|
COLORREF color, UINT format)
|
|
{
|
|
WCHAR wide[32];
|
|
int n = 0;
|
|
for (const char *s = text; *s && n < 31; ++s)
|
|
{
|
|
wide[n++] = (WCHAR)*s;
|
|
}
|
|
wide[n] = 0;
|
|
HFONT old_font = (HFONT)SelectObject(dc, font);
|
|
SetTextColor(dc, color);
|
|
DrawTextW(dc, wide, -1, rect, format);
|
|
SelectObject(dc, old_font);
|
|
}
|
|
|
|
static void
|
|
PaintPanel(HWND window)
|
|
{
|
|
PAINTSTRUCT paint;
|
|
HDC winDC = BeginPaint(window, &paint);
|
|
unsigned long tick = GetTickCount();
|
|
|
|
RECT client;
|
|
GetClientRect(window, &client);
|
|
|
|
// Issue #13 (flicker): the 100ms repaint timer redrew the panel
|
|
// control-by-control straight onto the window DC -- visible progressive
|
|
// flicker at 10 Hz. Compose the whole frame into a memory bitmap and
|
|
// blit it once (plus WM_ERASEBKGND suppressed in the wndproc). The
|
|
// drawing body below is untouched: `dc` is now the memory DC.
|
|
HDC dc = CreateCompatibleDC(winDC);
|
|
HBITMAP backing = CreateCompatibleBitmap(winDC,
|
|
client.right - client.left, client.bottom - client.top);
|
|
HBITMAP oldBacking = (HBITMAP)SelectObject(dc, backing);
|
|
HBRUSH background = CreateSolidBrush(RGB(28, 28, 28));
|
|
FillRect(dc, &client, background);
|
|
DeleteObject(background);
|
|
SetBkMode(dc, TRANSPARENT);
|
|
|
|
//
|
|
// Group frames + titles.
|
|
//
|
|
HPEN frame_pen = CreatePen(PS_SOLID, 1, RGB(80, 80, 80));
|
|
HPEN old_pen = (HPEN)SelectObject(dc, frame_pen);
|
|
HBRUSH old_brush = (HBRUSH)SelectObject(dc, GetStockObject(NULL_BRUSH));
|
|
for (int g = 0; g < PanelGroupCount; ++g)
|
|
{
|
|
const PanelGroupDef &group = panelGroups[g];
|
|
int x = group.originCol * CellW + 1;
|
|
int y = PanelMargin + (group.originRow - FirstRow) * CellH + 1;
|
|
Rectangle(dc, x, y,
|
|
x + group.cols * CellW, y + (group.rows + 1) * CellH);
|
|
RECT title_rect = { x + 4, y, x + group.cols * CellW - 4, y + CellH };
|
|
DrawLabelText(dc, group.title, titleFont, &title_rect,
|
|
RGB(220, 220, 220),
|
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
|
|
}
|
|
SelectObject(dc, old_brush);
|
|
SelectObject(dc, old_pen);
|
|
DeleteObject(frame_pen);
|
|
|
|
//
|
|
// Cells.
|
|
//
|
|
for (int i = 0; i < panelCellCount; ++i)
|
|
{
|
|
const PanelCell &cell = panelCells[i];
|
|
const RECT &r = cell.rect;
|
|
int held = (cell.address == pressedAddress) || latched[cell.address & 0x7F];
|
|
int lamp_capable = cell.group->lampCapable;
|
|
int yellow = (strcmp(cell.group->title, "Sec") == 0 ||
|
|
strcmp(cell.group->title, "Scr") == 0);
|
|
|
|
//
|
|
// Lamp shade: a locally held cap floods bright; else the host-
|
|
// commanded lamp state (with flash phase) decides.
|
|
//
|
|
int shade = 0;
|
|
if (lamp_capable)
|
|
{
|
|
shade = LampBrightnessOf(PadRIO::GetLampState(cell.address), tick);
|
|
}
|
|
if (held)
|
|
{
|
|
shade = 3;
|
|
}
|
|
|
|
COLORREF fill;
|
|
COLORREF text_color;
|
|
if (!lamp_capable)
|
|
{
|
|
fill = held ? RGB(205, 228, 255) : RGB(150, 182, 226);
|
|
text_color = RGB(0, 0, 0);
|
|
}
|
|
else if (yellow)
|
|
{
|
|
fill = (shade == 3) ? RGB(245, 210, 60)
|
|
: (shade != 0) ? RGB(140, 118, 38)
|
|
: RGB(70, 60, 24);
|
|
text_color = RGB(0, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
fill = (shade == 3) ? RGB(230, 70, 70)
|
|
: (shade != 0) ? RGB(120, 50, 50)
|
|
: RGB(64, 40, 40);
|
|
text_color = RGB(255, 255, 255);
|
|
}
|
|
HBRUSH face = CreateSolidBrush(fill);
|
|
RECT fill_rect = r;
|
|
FillRect(dc, &fill_rect, face);
|
|
DeleteObject(face);
|
|
|
|
//
|
|
// Label: keypads show the hex digit; named controls show the name
|
|
// over the small hex address; the rest show the address.
|
|
//
|
|
char hex[8];
|
|
sprintf(hex, "%02X", cell.address);
|
|
const char *name = PhysicalName(cell.address);
|
|
if (cell.group->kind == KindKeypad)
|
|
{
|
|
char digit[4];
|
|
sprintf(digit, "%X", cell.address & 0x0F);
|
|
RECT cell_rect = r;
|
|
DrawLabelText(dc, digit, buttonFont, &cell_rect, text_color,
|
|
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
else if (name != NULL)
|
|
{
|
|
RECT top_rect = { r.left, r.top + 1, r.right, (r.top + r.bottom) / 2 + 3 };
|
|
RECT bottom_rect = { r.left, (r.top + r.bottom) / 2, r.right, r.bottom - 1 };
|
|
DrawLabelText(dc, name, buttonFont, &top_rect, text_color,
|
|
DT_CENTER | DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS);
|
|
DrawLabelText(dc, hex, subFont, &bottom_rect, text_color,
|
|
DT_CENTER | DT_BOTTOM | DT_SINGLELINE);
|
|
}
|
|
else
|
|
{
|
|
RECT cell_rect = r;
|
|
DrawLabelText(dc, hex, buttonFont, &cell_rect, text_color,
|
|
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
|
|
//
|
|
// Latch = gold outline, held = white outline (vRIO).
|
|
//
|
|
if (latched[cell.address & 0x7F])
|
|
{
|
|
HBRUSH gold = CreateSolidBrush(RGB(255, 215, 0));
|
|
RECT outline = r;
|
|
FrameRect(dc, &outline, gold);
|
|
outline.left += 1; outline.top += 1;
|
|
outline.right -= 1; outline.bottom -= 1;
|
|
FrameRect(dc, &outline, gold);
|
|
DeleteObject(gold);
|
|
}
|
|
else if (held)
|
|
{
|
|
RECT outline = r;
|
|
FrameRect(dc, &outline, (HBRUSH)GetStockObject(WHITE_BRUSH));
|
|
outline.left += 1; outline.top += 1;
|
|
outline.right -= 1; outline.bottom -= 1;
|
|
FrameRect(dc, &outline, (HBRUSH)GetStockObject(WHITE_BRUSH));
|
|
}
|
|
}
|
|
|
|
// single blit of the composed frame (issue #13)
|
|
BitBlt(winDC, 0, 0, client.right - client.left, client.bottom - client.top,
|
|
dc, 0, 0, SRCCOPY);
|
|
SelectObject(dc, oldBacking);
|
|
DeleteObject(backing);
|
|
DeleteDC(dc);
|
|
|
|
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_ERASEBKGND:
|
|
return 1; // issue #13: PaintPanel fills the frame itself (double-buffered)
|
|
|
|
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)
|
|
{
|
|
if (latched[address & 0x7F])
|
|
{
|
|
// Left-clicking a latched button releases it.
|
|
latched[address & 0x7F] = 0;
|
|
PadRIO::SetScreenButton(address, 0);
|
|
}
|
|
else
|
|
{
|
|
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_RBUTTONDOWN:
|
|
{
|
|
// Latch toggle: press-and-hold without keeping the mouse down.
|
|
int address = HitTest((int)(short)LOWORD(lparam),
|
|
(int)(short)HIWORD(lparam));
|
|
if (address >= 0)
|
|
{
|
|
if (latched[address & 0x7F])
|
|
{
|
|
latched[address & 0x7F] = 0;
|
|
PadRIO::SetScreenButton(address, 0);
|
|
}
|
|
else
|
|
{
|
|
latched[address & 0x7F] = 1;
|
|
PadRIO::SetScreenButton(address, 1);
|
|
}
|
|
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;
|
|
}
|
|
|
|
LayoutCells();
|
|
memset(latched, 0, sizeof(latched));
|
|
|
|
titleFont = CreateFontW(-11, 0, 0, 0, FW_BOLD, 0, 0, 0,
|
|
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, L"Segoe UI");
|
|
buttonFont = CreateFontW(-10, 0, 0, 0, FW_NORMAL, 0, 0, 0,
|
|
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, L"Segoe UI");
|
|
subFont = CreateFontW(-9, 0, 0, 0, FW_NORMAL, 0, 0, 0,
|
|
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, L"Segoe UI");
|
|
|
|
int client_width = 0, client_height = 0;
|
|
for (int i = 0; i < panelCellCount; ++i)
|
|
{
|
|
if (panelCells[i].rect.right > client_width)
|
|
{
|
|
client_width = panelCells[i].rect.right;
|
|
}
|
|
if (panelCells[i].rect.bottom > client_height)
|
|
{
|
|
client_height = panelCells[i].rect.bottom;
|
|
}
|
|
}
|
|
client_width += PanelMargin;
|
|
client_height += 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);
|
|
|
|
//
|
|
// Park the panel at the RIGHT edge of the screen, TOPMOST: the game
|
|
// window opens after us and would otherwise bury the panel (found in
|
|
// the first dist: "there is no glass panel"). The pod's physical
|
|
// button banks are always visible -- so is the glass panel.
|
|
//
|
|
int frame_width = frame.right - frame.left;
|
|
int frame_height = frame.bottom - frame.top;
|
|
int screen_width = GetSystemMetrics(SM_CXSCREEN);
|
|
int panel_x = screen_width - frame_width - 12;
|
|
if (panel_x < 0)
|
|
{
|
|
panel_x = 0;
|
|
}
|
|
|
|
// Issue #13: WS_EX_LAYERED -- a layered window is composited by DWM
|
|
// independently of the game's D3D swap chain, ending the D3D-vs-GDI
|
|
// presentation contention that strobed the panel on the game's monitor
|
|
// (single-monitor setups had no escape). Fully opaque; behavior is
|
|
// otherwise identical.
|
|
panelWindow = CreateWindowExW(
|
|
WS_EX_TOPMOST | WS_EX_LAYERED,
|
|
L"BTPadPanelWnd", L"BattleTech - Cockpit Buttons",
|
|
style,
|
|
panel_x, 12,
|
|
frame_width, frame_height,
|
|
NULL, NULL, GetModuleHandleW(NULL), NULL);
|
|
if (panelWindow == NULL)
|
|
{
|
|
DEBUG_STREAM << "[padpanel] CreateWindow failed\n" << std::flush;
|
|
return;
|
|
}
|
|
SetLayeredWindowAttributes(panelWindow, 0, 255, LWA_ALPHA); // opaque layer
|
|
SetTimer(panelWindow, RepaintTimerId, RepaintMilliseconds, NULL);
|
|
ShowWindow(panelWindow, SW_SHOWNOACTIVATE);
|
|
DEBUG_STREAM << "[padpanel] cockpit button panel up ("
|
|
<< panelCellCount << " controls, vRIO layout)\n" << std::flush;
|
|
}
|
|
|
|
void
|
|
BTPadPanel_Destroy()
|
|
{
|
|
if (panelWindow != NULL)
|
|
{
|
|
KillTimer(panelWindow, RepaintTimerId);
|
|
DestroyWindow(panelWindow);
|
|
panelWindow = NULL;
|
|
}
|
|
if (titleFont) { DeleteObject(titleFont); titleFont = NULL; }
|
|
if (buttonFont) { DeleteObject(buttonFont); buttonFont = NULL; }
|
|
if (subFont) { DeleteObject(subFont); subFont = NULL; }
|
|
}
|