Cockpit buttons on the split displays, lamp-lit and clickable
Each MFDSplitView window now carries its display's physical button bank, placed as in the pod (addresses per vRIO CockpitLayout): a 4x2 red cluster around each MFD glass (anchors 0x2F/0x27/0x37 upper, 0x0F/0x07 lower, addresses descending row-major) and 6 amber buttons down each side of the map - Secondary 0x10-0x15 left, Screen 0x18-0x1D right; the remaining column addresses are Tesla relays, per the pod wiring, so they get no buttons. Buttons light from the lamp state the game commands: PadRIO grows a static active-instance hook (SetScreenButton/GetLampState); mouse press/release feeds PadRIO's desired-state sampling alongside pad and keyboard, and paint decodes the lamp byte (state1/state2 brightness, solid/slow/med/fast flash animated by tick). With real serial hardware (no PadRIO) the buttons draw dark and inert. Verified: map flank buttons light per the game's preset lamps, aligned with the labels the glass draws at its edges; MFD clusters render 4+4. Roadmap: queued the vRIO Dynamic Lighting RGB-keyboard lamp mirror as a polish-pass item. dist repacked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+257
-45
@@ -2,47 +2,83 @@
|
||||
#pragma hdrstop
|
||||
|
||||
#include "l4mfdview.h"
|
||||
#include "l4padrio.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const char mfdViewClass[] = "RPMFDView";
|
||||
const char mfdViewProp[] = "RPMFDViewBlit";
|
||||
|
||||
struct MFDViewBlit
|
||||
const int buttonGap = 4;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Button fill colors by lamp brightness (0 off / 1-2 dim / 3
|
||||
// bright), red family for MFD strips, amber for the side columns.
|
||||
//---------------------------------------------------------------
|
||||
COLORREF ButtonFill(int amber, int level)
|
||||
{
|
||||
BITMAPINFOHEADER header;
|
||||
void *pixels;
|
||||
int sourceWidth;
|
||||
int sourceHeight;
|
||||
};
|
||||
if (amber)
|
||||
{
|
||||
if (level >= 3) return RGB(255, 210, 48);
|
||||
if (level >= 1) return RGB(150, 124, 26);
|
||||
return RGB(64, 52, 10);
|
||||
}
|
||||
if (level >= 3) return RGB(255, 72, 44);
|
||||
if (level >= 1) return RGB(150, 44, 28);
|
||||
return RGB(64, 18, 12);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Lamp byte -> current brightness level, animating flash modes.
|
||||
// Low 2 bits: solid/slow/med/fast; bits 2-3: state 1 brightness;
|
||||
// bits 4-5: state 2 brightness (the flash alternate).
|
||||
//---------------------------------------------------------------
|
||||
int LampLevel(int lamp_state)
|
||||
{
|
||||
int mode = lamp_state & 0x03;
|
||||
int level1 = (lamp_state >> 2) & 0x03;
|
||||
int level2 = (lamp_state >> 4) & 0x03;
|
||||
|
||||
if (mode == 0)
|
||||
{
|
||||
return level1;
|
||||
}
|
||||
|
||||
static const int half_period[4] = { 0, 500, 250, 125 };
|
||||
return ((GetTickCount() / half_period[mode]) & 1) ? level2 : level1;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK
|
||||
MFDViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
MFDSplitView *view =
|
||||
(MFDSplitView *) GetWindowLongPtrA(hwnd, GWLP_USERDATA);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case WM_PAINT:
|
||||
if (view != NULL)
|
||||
{
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = BeginPaint(hwnd, &ps);
|
||||
const MFDViewBlit *blit =
|
||||
(const MFDViewBlit *) GetPropA(hwnd, mfdViewProp);
|
||||
if (blit != NULL && blit->pixels != NULL)
|
||||
{
|
||||
RECT client;
|
||||
GetClientRect(hwnd, &client);
|
||||
SetStretchBltMode(hdc, COLORONCOLOR);
|
||||
StretchDIBits(
|
||||
hdc,
|
||||
0, 0, client.right, client.bottom,
|
||||
0, 0, blit->sourceWidth, blit->sourceHeight,
|
||||
blit->pixels, (const BITMAPINFO *) &blit->header,
|
||||
DIB_RGB_COLORS, SRCCOPY
|
||||
);
|
||||
}
|
||||
EndPaint(hwnd, &ps);
|
||||
view->Paint();
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
break;
|
||||
|
||||
case WM_LBUTTONDOWN:
|
||||
if (view != NULL)
|
||||
{
|
||||
view->MouseDown((int)(short) LOWORD(lParam), (int)(short) HIWORD(lParam));
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_LBUTTONUP:
|
||||
case WM_CAPTURECHANGED:
|
||||
if (view != NULL)
|
||||
{
|
||||
view->MouseUp();
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_CLOSE:
|
||||
// Part of the cockpit; just hide it.
|
||||
@@ -56,36 +92,47 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
//########################################################################
|
||||
//############################ MFDSplitView ##############################
|
||||
//########################################################################
|
||||
|
||||
MFDSplitView::MFDSplitView(
|
||||
const char *title,
|
||||
int source_width,
|
||||
int source_height,
|
||||
int client_width,
|
||||
int client_height,
|
||||
int display_width,
|
||||
int display_height,
|
||||
int x,
|
||||
int y
|
||||
int y,
|
||||
ButtonStyle button_style,
|
||||
int anchorA,
|
||||
int anchorB
|
||||
)
|
||||
{
|
||||
Check_Pointer(this);
|
||||
|
||||
sourceWidth = source_width;
|
||||
sourceHeight = source_height;
|
||||
buttonCount = 0;
|
||||
pressedIndex = -1;
|
||||
|
||||
pixels = new unsigned long[source_width * source_height];
|
||||
memset(pixels, 0, source_width * source_height * sizeof(unsigned long));
|
||||
|
||||
MFDViewBlit *blit = new MFDViewBlit;
|
||||
memset(blit, 0, sizeof(MFDViewBlit));
|
||||
blit->header.biSize = sizeof(BITMAPINFOHEADER);
|
||||
blit->header.biWidth = source_width;
|
||||
blit->header.biHeight = -source_height; // top-down
|
||||
blit->header.biPlanes = 1;
|
||||
blit->header.biBitCount = 32;
|
||||
blit->header.biCompression = BI_RGB;
|
||||
blit->pixels = pixels;
|
||||
blit->sourceWidth = source_width;
|
||||
blit->sourceHeight = source_height;
|
||||
blitInfo = blit;
|
||||
BITMAPINFOHEADER *header = new BITMAPINFOHEADER;
|
||||
memset(header, 0, sizeof(BITMAPINFOHEADER));
|
||||
header->biSize = sizeof(BITMAPINFOHEADER);
|
||||
header->biWidth = source_width;
|
||||
header->biHeight = -source_height; // top-down
|
||||
header->biPlanes = 1;
|
||||
header->biBitCount = 32;
|
||||
header->biCompression = BI_RGB;
|
||||
blitHeader = header;
|
||||
|
||||
int client_width = display_width;
|
||||
int client_height = display_height;
|
||||
LayoutButtons(button_style, anchorA, anchorB,
|
||||
display_width, display_height, &client_width, &client_height);
|
||||
|
||||
HINSTANCE instance = GetModuleHandleA(NULL);
|
||||
|
||||
@@ -126,7 +173,7 @@ MFDSplitView::MFDSplitView(
|
||||
|
||||
if (window != NULL)
|
||||
{
|
||||
SetPropA((HWND) window, mfdViewProp, blitInfo);
|
||||
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
|
||||
ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
|
||||
}
|
||||
else
|
||||
@@ -141,12 +188,12 @@ MFDSplitView::~MFDSplitView()
|
||||
|
||||
if (window != NULL)
|
||||
{
|
||||
RemovePropA((HWND) window, mfdViewProp);
|
||||
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, 0);
|
||||
DestroyWindow((HWND) window);
|
||||
window = NULL;
|
||||
}
|
||||
delete (MFDViewBlit *) blitInfo;
|
||||
blitInfo = NULL;
|
||||
delete (BITMAPINFOHEADER *) blitHeader;
|
||||
blitHeader = NULL;
|
||||
delete [] pixels;
|
||||
pixels = NULL;
|
||||
}
|
||||
@@ -157,6 +204,171 @@ Logical
|
||||
return pixels != NULL;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Compute the display rectangle and the button rectangles, growing the
|
||||
// client area to make room for the strips.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
void
|
||||
MFDSplitView::LayoutButtons(
|
||||
ButtonStyle button_style,
|
||||
int anchorA,
|
||||
int anchorB,
|
||||
int display_width,
|
||||
int display_height,
|
||||
int *client_width,
|
||||
int *client_height
|
||||
)
|
||||
{
|
||||
displayX = 0;
|
||||
displayY = 0;
|
||||
displayW = display_width;
|
||||
displayH = display_height;
|
||||
|
||||
if (button_style == MFDStrips)
|
||||
{
|
||||
//-----------------------------------------------------------
|
||||
// 4 buttons above the glass, 4 below; RIO addresses descend
|
||||
// from the anchor, row-major (vRIO CockpitLayout::Mfd).
|
||||
//-----------------------------------------------------------
|
||||
int strip_h = display_height / 8;
|
||||
if (strip_h < 18) strip_h = 18;
|
||||
if (strip_h > 40) strip_h = 40;
|
||||
|
||||
int button_w = (display_width - 5 * buttonGap) / 4;
|
||||
|
||||
displayY = strip_h + 2 * buttonGap;
|
||||
*client_height = display_height + 2 * (strip_h + 2 * buttonGap);
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
int row = i / 4; // 0 = top strip, 1 = bottom strip
|
||||
int col = i % 4;
|
||||
|
||||
ScreenButton *button = &buttons[buttonCount++];
|
||||
button->unit = anchorA - i;
|
||||
button->amber = 0;
|
||||
button->x = buttonGap + col * (button_w + buttonGap);
|
||||
button->y = row ? (displayY + displayH + buttonGap) : buttonGap;
|
||||
button->w = button_w;
|
||||
button->h = strip_h;
|
||||
}
|
||||
}
|
||||
else if (button_style == SideColumns)
|
||||
{
|
||||
//-----------------------------------------------------------
|
||||
// 6 buttons down each side of the glass (the pod wires only
|
||||
// 6 of each column's 8 addresses to buttons; the rest are
|
||||
// Tesla relays). Left = anchorA ascending, right = anchorB.
|
||||
//-----------------------------------------------------------
|
||||
int col_w = display_width / 8;
|
||||
if (col_w < 20) col_w = 20;
|
||||
if (col_w > 40) col_w = 40;
|
||||
|
||||
int button_h = (display_height - 7 * buttonGap) / 6;
|
||||
|
||||
displayX = col_w + 2 * buttonGap;
|
||||
*client_width = display_width + 2 * (col_w + 2 * buttonGap);
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
ScreenButton *left = &buttons[buttonCount++];
|
||||
left->unit = anchorA + i;
|
||||
left->amber = 1;
|
||||
left->x = buttonGap;
|
||||
left->y = buttonGap + i * (button_h + buttonGap);
|
||||
left->w = col_w;
|
||||
left->h = button_h;
|
||||
|
||||
ScreenButton *right = &buttons[buttonCount++];
|
||||
right->unit = anchorB + i;
|
||||
right->amber = 1;
|
||||
right->x = displayX + displayW + buttonGap;
|
||||
right->y = left->y;
|
||||
right->w = col_w;
|
||||
right->h = button_h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MFDSplitView::Paint()
|
||||
{
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = BeginPaint((HWND) window, &ps);
|
||||
|
||||
RECT client;
|
||||
GetClientRect((HWND) window, &client);
|
||||
FillRect(hdc, &client, (HBRUSH) GetStockObject(BLACK_BRUSH));
|
||||
|
||||
SetStretchBltMode(hdc, COLORONCOLOR);
|
||||
StretchDIBits(
|
||||
hdc,
|
||||
displayX, displayY, displayW, displayH,
|
||||
0, 0, sourceWidth, sourceHeight,
|
||||
pixels, (const BITMAPINFO *) blitHeader,
|
||||
DIB_RGB_COLORS, SRCCOPY
|
||||
);
|
||||
|
||||
for (int i = 0; i < buttonCount; ++i)
|
||||
{
|
||||
const ScreenButton *button = &buttons[i];
|
||||
|
||||
int level = PadRIO::IsActive()
|
||||
? LampLevel(PadRIO::GetLampState(button->unit))
|
||||
: 0;
|
||||
|
||||
RECT rect;
|
||||
rect.left = button->x;
|
||||
rect.top = button->y;
|
||||
rect.right = button->x + button->w;
|
||||
rect.bottom = button->y + button->h;
|
||||
|
||||
HBRUSH fill = CreateSolidBrush(ButtonFill(button->amber, level));
|
||||
FillRect(hdc, &rect, fill);
|
||||
DeleteObject(fill);
|
||||
|
||||
HBRUSH frame = CreateSolidBrush(
|
||||
(i == pressedIndex) ? RGB(255, 255, 255) : RGB(48, 48, 48));
|
||||
FrameRect(hdc, &rect, frame);
|
||||
DeleteObject(frame);
|
||||
}
|
||||
|
||||
EndPaint((HWND) window, &ps);
|
||||
}
|
||||
|
||||
void
|
||||
MFDSplitView::MouseDown(int x, int y)
|
||||
{
|
||||
for (int i = 0; i < buttonCount; ++i)
|
||||
{
|
||||
const ScreenButton *button = &buttons[i];
|
||||
if (x >= button->x && x < button->x + button->w &&
|
||||
y >= button->y && y < button->y + button->h)
|
||||
{
|
||||
pressedIndex = i;
|
||||
SetCapture((HWND) window);
|
||||
PadRIO::SetScreenButton(button->unit, True);
|
||||
InvalidateRect((HWND) window, NULL, FALSE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MFDSplitView::MouseUp()
|
||||
{
|
||||
if (pressedIndex >= 0)
|
||||
{
|
||||
PadRIO::SetScreenButton(buttons[pressedIndex].unit, False);
|
||||
pressedIndex = -1;
|
||||
if (GetCapture() == (HWND) window)
|
||||
{
|
||||
ReleaseCapture();
|
||||
}
|
||||
InvalidateRect((HWND) window, NULL, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MFDSplitView::Repaint()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user