Cockpit: on-screen vRIO button banks around the MFDs (clickable, lamp-lit)
Each MFD carries its 4+4 physical button strip and the radar its 6+6 amber side columns, exactly as mounted on the pod (RP412 MFDSplitView geometry + the shared Tesla-board RIO addresses; anchors 0x2F/0x27/0x37/ 0x0F/0x07 for the five MFDs, 0x10/0x18 for the radar). - L4VB16.cpp: BTBuildCockpitButtons computes the button rects from the same cockpit surface layout (strips toward the screen interior to stay on-canvas); BTDrawCockpitButtons draws them as coloured quads, lit from PadRIO::GetLampState (bright red/amber) or dim; state-block wrapped so the button pass doesn't leak render state into the next world frame. BTCockpitButtonAt hit-tests; BTPadRIOScreenButton bridges to PadRIO. - btl4main.cpp WndProc: WM_LBUTTONDOWN/UP hit-test the buttons and inject press/release into PadRIO (SetCapture so the release always fires). Verified: the button banks render around all six surfaces in the L4MFDSPLIT cockpit; mouse plumbing wired through the already-active PadRIO screen-button path (Phase 2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include "mungal4.h"
|
||||
#include "l4padrio.h" // PadRIO::GetLampState / SetScreenButton (cockpit buttons)
|
||||
#pragma hdrstop
|
||||
|
||||
#include "l4vb16.h"
|
||||
@@ -450,6 +451,141 @@ void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device)
|
||||
// runs at the FULL viewport in this mode (no strip carve-out), so the surfaces
|
||||
// occlude the corners like the pod bezels. Called from L4VIDEO alongside the
|
||||
// dev-gauge inset; the two modes are mutually exclusive.
|
||||
//
|
||||
// ON-SCREEN BUTTON BANKS (the vRIO control surface, on the cockpit glass):
|
||||
// each MFD carries its 4+4 physical button strip and the radar its 6+6 side
|
||||
// columns, exactly as mounted on the pod (RP412 MFDSplitView geometry, shared
|
||||
// Tesla-board RIO addresses). Mouse presses inject into PadRIO
|
||||
// (SetScreenButton); the game's lamp commands light them (GetLampState).
|
||||
// Positions are computed from the same cockpit surface layout each frame so
|
||||
// draw and hit-test agree. MFD strips sit toward the screen interior (below
|
||||
// the top-row glass, above the bottom-row glass) to stay on-canvas.
|
||||
//
|
||||
struct BTCockpitBtn { int unit; int amber; float x, y, w, h; };
|
||||
|
||||
// {surface index in kBTCockpitSurfaces, anchorA, anchorB, style, topRow}
|
||||
// style: 0 = MFDStrips (4+4, unit = anchorA - i); 1 = SideColumns (6+6).
|
||||
static const struct BTBtnBank {
|
||||
int surf; int anchorA; int anchorB; int style; int topRow;
|
||||
} kBTBtnBanks[6] = {
|
||||
{ 0, 0x2F, 0, 0, 1 }, // Heat (UL) strips below
|
||||
{ 1, 0x27, 0, 0, 1 }, // Mfd2 (UC) strips below
|
||||
{ 2, 0x37, 0, 0, 1 }, // Comm (UR) strips below
|
||||
{ 3, 0x0F, 0, 0, 0 }, // Mfd1 (LL) strips above
|
||||
{ 4, 0x07, 0, 0, 0 }, // Mfd3 (LR) strips above
|
||||
{ 5, 0x10, 0x18, 1, 0 },// radar (sec) side columns 0x10 left / 0x18 right
|
||||
};
|
||||
|
||||
static int BTBuildCockpitButtons(float W, float H, BTCockpitBtn *out, int maxOut)
|
||||
{
|
||||
int n = 0;
|
||||
for (int b = 0; b < 6 && n < maxOut; ++b)
|
||||
{
|
||||
const BTBtnBank &bank = kBTBtnBanks[b];
|
||||
const BTGaugeSurfaceDesc &s = kBTCockpitSurfaces[bank.surf];
|
||||
float sx = s.cellX * W, sy = s.cellY * H;
|
||||
float sw = s.cellW * W, sh = s.cellH * H;
|
||||
|
||||
if (bank.style == 0)
|
||||
{
|
||||
// 4+4 grid; strip height ~ glass/8 clamped, width = glass/4.
|
||||
float striph = sh / 6.0f; if (striph < 14.0f) striph = 14.0f; if (striph > 44.0f) striph = 44.0f;
|
||||
float bw = sw / 4.0f;
|
||||
// the two rows sit toward the screen interior
|
||||
float row0y = bank.topRow ? (sy + sh) : (sy - 2.0f * striph);
|
||||
float row1y = bank.topRow ? (sy + sh + striph) : (sy - striph);
|
||||
for (int i = 0; i < 8 && n < maxOut; ++i)
|
||||
{
|
||||
int row = i / 4, col = i % 4;
|
||||
BTCockpitBtn &bt = out[n++];
|
||||
bt.unit = bank.anchorA - i;
|
||||
bt.amber = 0;
|
||||
bt.x = sx + col * bw;
|
||||
bt.y = (row == 0) ? row0y : row1y;
|
||||
bt.w = bw - 2.0f;
|
||||
bt.h = striph - 2.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 6 per side, flush to the glass edges.
|
||||
float colw = sw / 6.0f; if (colw < 16.0f) colw = 16.0f; if (colw > 40.0f) colw = 40.0f;
|
||||
float bh = sh / 6.0f;
|
||||
for (int i = 0; i < 6 && n + 1 < maxOut; ++i)
|
||||
{
|
||||
BTCockpitBtn &l = out[n++];
|
||||
l.unit = bank.anchorA + i; l.amber = 1;
|
||||
l.x = sx - colw; l.y = sy + i * bh; l.w = colw - 2.0f; l.h = bh - 2.0f;
|
||||
BTCockpitBtn &r = out[n++];
|
||||
r.unit = bank.anchorB + i; r.amber = 1;
|
||||
r.x = sx + sw + 2.0f; r.y = sy + i * bh; r.w = colw - 2.0f; r.h = bh - 2.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// A flat coloured quad (no texture) for the buttons.
|
||||
static void BTFillRect(LPDIRECT3DDEVICE9 device, float x, float y, float w, float h, DWORD color)
|
||||
{
|
||||
struct CVert { float x, y, z, rhw; DWORD c; };
|
||||
CVert quad[4] = {
|
||||
{ x, y, 0.0f, 1.0f, color },
|
||||
{ x + w, y, 0.0f, 1.0f, color },
|
||||
{ x + w, y + h, 0.0f, 1.0f, color },
|
||||
{ x, y + h, 0.0f, 1.0f, color },
|
||||
};
|
||||
device->SetTexture(0, NULL);
|
||||
device->SetFVF(D3DFVF_XYZRHW | D3DFVF_DIFFUSE);
|
||||
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
|
||||
device->SetRenderState(D3DRS_ZENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||
device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, quad, sizeof(CVert));
|
||||
}
|
||||
|
||||
static void BTDrawCockpitButtons(LPDIRECT3DDEVICE9 device, float W, float H)
|
||||
{
|
||||
BTCockpitBtn btn[64];
|
||||
int count = BTBuildCockpitButtons(W, H, btn, 64);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
int lit = PadRIO::IsActive() ? PadRIO::GetLampState(btn[i].unit) : 0;
|
||||
DWORD color;
|
||||
if (btn[i].amber)
|
||||
color = lit ? 0xFFFFB000 : 0xFF3A2A00; // amber lit / dim
|
||||
else
|
||||
color = lit ? 0xFFFF2020 : 0xFF3A0808; // red lit / dim
|
||||
// a thin dark border reads as a raised key
|
||||
BTFillRect(device, btn[i].x - 1, btn[i].y - 1, btn[i].w + 2, btn[i].h + 2, 0xFF101010);
|
||||
BTFillRect(device, btn[i].x, btn[i].y, btn[i].w, btn[i].h, color);
|
||||
}
|
||||
}
|
||||
|
||||
// Bridge for the main WndProc (which does not include l4padrio.h): inject a
|
||||
// screen-button press/release into the active PadRIO.
|
||||
void BTPadRIOScreenButton(int unit, int pressed)
|
||||
{
|
||||
if (PadRIO::IsActive())
|
||||
PadRIO::SetScreenButton(unit, pressed ? True : False);
|
||||
}
|
||||
|
||||
// Mouse hit-test (window CLIENT pixels): the RIO unit under (px,py), or -1.
|
||||
// Called from the main WndProc; needs the current window client size.
|
||||
int BTCockpitButtonAt(int px, int py, int clientW, int clientH)
|
||||
{
|
||||
if (!DevCockpitPanel()) return -1;
|
||||
BTCockpitBtn btn[64];
|
||||
int count = BTBuildCockpitButtons((float)clientW, (float)clientH, btn, 64);
|
||||
for (int i = 0; i < count; ++i)
|
||||
if (px >= btn[i].x && px < btn[i].x + btn[i].w &&
|
||||
py >= btn[i].y && py < btn[i].y + btn[i].h)
|
||||
return btn[i].unit;
|
||||
return -1;
|
||||
}
|
||||
|
||||
//
|
||||
void BTDrawCockpitPanel(LPDIRECT3DDEVICE9 device)
|
||||
{
|
||||
@@ -461,6 +597,13 @@ void BTDrawCockpitPanel(LPDIRECT3DDEVICE9 device)
|
||||
rt->Release();
|
||||
BTDrawGaugeSurfaces(device, 0.0f, 0.0f, (float)d.Width, (float)d.Height,
|
||||
kBTCockpitSurfaces);
|
||||
// The button quads dirty device state (texture/FVF/render states); snapshot
|
||||
// and restore around them so the next world frame isn't rendered with them
|
||||
// (the "floating rocks" depth-state leak documented in BTDrawGaugeSurfaces).
|
||||
IDirect3DStateBlock9 *sb = NULL;
|
||||
device->CreateStateBlock(D3DSBT_ALL, &sb);
|
||||
BTDrawCockpitButtons(device, (float)d.Width, (float)d.Height);
|
||||
if (sb != NULL) { sb->Apply(); sb->Release(); }
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
|
||||
@@ -100,6 +100,29 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
L4NotifyWindowResized((int)LOWORD(lParam), (int)HIWORD(lParam));
|
||||
}
|
||||
return 0;
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
{
|
||||
// COCKPIT BUTTONS (L4MFDSPLIT): a mouse press on an on-screen MFD/radar
|
||||
// button injects into PadRIO exactly like a physical RIO key. A single
|
||||
// pressed unit is tracked so the matching release fires even if the
|
||||
// cursor moved off the button first.
|
||||
extern int BTCockpitButtonAt(int px, int py, int clientW, int clientH);
|
||||
extern void BTPadRIOScreenButton(int unit, int pressed);
|
||||
static int s_pressedUnit = -1;
|
||||
RECT rc; GetClientRect(hWnd, &rc);
|
||||
int mx = (short)LOWORD(lParam), my = (short)HIWORD(lParam);
|
||||
if (uMsg == WM_LBUTTONDOWN)
|
||||
{
|
||||
int unit = BTCockpitButtonAt(mx, my, rc.right, rc.bottom);
|
||||
if (unit >= 0) { s_pressedUnit = unit; BTPadRIOScreenButton(unit, 1); SetCapture(hWnd); }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s_pressedUnit >= 0) { BTPadRIOScreenButton(s_pressedUnit, 0); s_pressedUnit = -1; ReleaseCapture(); }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case WM_CLOSE:
|
||||
DestroyWindow(hWnd);
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user