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:
@@ -126,6 +126,17 @@ Two new environment options remove the hardware dependency entirely:
|
|||||||
view; all windows are draggable. This replaces the external
|
view; all windows are draggable. This replaces the external
|
||||||
BitBlt-mirror launcher wrapper.
|
BitBlt-mirror launcher wrapper.
|
||||||
|
|
||||||
|
Each split window also carries its display's **physical button bank**
|
||||||
|
(geometry per vRIO's `CockpitLayout`): 4 red buttons above and below
|
||||||
|
each MFD glass (RIO addresses descending from the cluster anchor —
|
||||||
|
upper left 0x2F, upper center 0x27, upper right 0x37, lower left 0x0F,
|
||||||
|
lower right 0x07), and 6 amber buttons down each side of the map
|
||||||
|
(Secondary 0x10–0x15 left, Screen 0x18–0x1D right; the columns' other
|
||||||
|
addresses are Tesla relays, not buttons). They **light from the lamp
|
||||||
|
states the game commands** (dim/bright, flash modes animate) and press
|
||||||
|
the corresponding RIO unit with the mouse — active when `PadRIO` is the
|
||||||
|
control device, dark and inert with real serial hardware.
|
||||||
|
|
||||||
Bindings (vRIO's default profile, condensed):
|
Bindings (vRIO's default profile, condensed):
|
||||||
|
|
||||||
| Input | Pod control |
|
| Input | Pod control |
|
||||||
|
|||||||
+257
-45
@@ -2,47 +2,83 @@
|
|||||||
#pragma hdrstop
|
#pragma hdrstop
|
||||||
|
|
||||||
#include "l4mfdview.h"
|
#include "l4mfdview.h"
|
||||||
|
#include "l4padrio.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
const char mfdViewClass[] = "RPMFDView";
|
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;
|
if (amber)
|
||||||
void *pixels;
|
{
|
||||||
int sourceWidth;
|
if (level >= 3) return RGB(255, 210, 48);
|
||||||
int sourceHeight;
|
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
|
LRESULT CALLBACK
|
||||||
MFDViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
MFDViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
|
MFDSplitView *view =
|
||||||
|
(MFDSplitView *) GetWindowLongPtrA(hwnd, GWLP_USERDATA);
|
||||||
|
|
||||||
switch (message)
|
switch (message)
|
||||||
{
|
{
|
||||||
case WM_PAINT:
|
case WM_PAINT:
|
||||||
|
if (view != NULL)
|
||||||
{
|
{
|
||||||
PAINTSTRUCT ps;
|
view->Paint();
|
||||||
HDC hdc = BeginPaint(hwnd, &ps);
|
return 0;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
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:
|
case WM_CLOSE:
|
||||||
// Part of the cockpit; just hide it.
|
// Part of the cockpit; just hide it.
|
||||||
@@ -56,36 +92,47 @@ namespace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//########################################################################
|
||||||
|
//############################ MFDSplitView ##############################
|
||||||
|
//########################################################################
|
||||||
|
|
||||||
MFDSplitView::MFDSplitView(
|
MFDSplitView::MFDSplitView(
|
||||||
const char *title,
|
const char *title,
|
||||||
int source_width,
|
int source_width,
|
||||||
int source_height,
|
int source_height,
|
||||||
int client_width,
|
int display_width,
|
||||||
int client_height,
|
int display_height,
|
||||||
int x,
|
int x,
|
||||||
int y
|
int y,
|
||||||
|
ButtonStyle button_style,
|
||||||
|
int anchorA,
|
||||||
|
int anchorB
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Check_Pointer(this);
|
Check_Pointer(this);
|
||||||
|
|
||||||
sourceWidth = source_width;
|
sourceWidth = source_width;
|
||||||
sourceHeight = source_height;
|
sourceHeight = source_height;
|
||||||
|
buttonCount = 0;
|
||||||
|
pressedIndex = -1;
|
||||||
|
|
||||||
pixels = new unsigned long[source_width * source_height];
|
pixels = new unsigned long[source_width * source_height];
|
||||||
memset(pixels, 0, source_width * source_height * sizeof(unsigned long));
|
memset(pixels, 0, source_width * source_height * sizeof(unsigned long));
|
||||||
|
|
||||||
MFDViewBlit *blit = new MFDViewBlit;
|
BITMAPINFOHEADER *header = new BITMAPINFOHEADER;
|
||||||
memset(blit, 0, sizeof(MFDViewBlit));
|
memset(header, 0, sizeof(BITMAPINFOHEADER));
|
||||||
blit->header.biSize = sizeof(BITMAPINFOHEADER);
|
header->biSize = sizeof(BITMAPINFOHEADER);
|
||||||
blit->header.biWidth = source_width;
|
header->biWidth = source_width;
|
||||||
blit->header.biHeight = -source_height; // top-down
|
header->biHeight = -source_height; // top-down
|
||||||
blit->header.biPlanes = 1;
|
header->biPlanes = 1;
|
||||||
blit->header.biBitCount = 32;
|
header->biBitCount = 32;
|
||||||
blit->header.biCompression = BI_RGB;
|
header->biCompression = BI_RGB;
|
||||||
blit->pixels = pixels;
|
blitHeader = header;
|
||||||
blit->sourceWidth = source_width;
|
|
||||||
blit->sourceHeight = source_height;
|
int client_width = display_width;
|
||||||
blitInfo = blit;
|
int client_height = display_height;
|
||||||
|
LayoutButtons(button_style, anchorA, anchorB,
|
||||||
|
display_width, display_height, &client_width, &client_height);
|
||||||
|
|
||||||
HINSTANCE instance = GetModuleHandleA(NULL);
|
HINSTANCE instance = GetModuleHandleA(NULL);
|
||||||
|
|
||||||
@@ -126,7 +173,7 @@ MFDSplitView::MFDSplitView(
|
|||||||
|
|
||||||
if (window != NULL)
|
if (window != NULL)
|
||||||
{
|
{
|
||||||
SetPropA((HWND) window, mfdViewProp, blitInfo);
|
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
|
||||||
ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
|
ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -141,12 +188,12 @@ MFDSplitView::~MFDSplitView()
|
|||||||
|
|
||||||
if (window != NULL)
|
if (window != NULL)
|
||||||
{
|
{
|
||||||
RemovePropA((HWND) window, mfdViewProp);
|
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, 0);
|
||||||
DestroyWindow((HWND) window);
|
DestroyWindow((HWND) window);
|
||||||
window = NULL;
|
window = NULL;
|
||||||
}
|
}
|
||||||
delete (MFDViewBlit *) blitInfo;
|
delete (BITMAPINFOHEADER *) blitHeader;
|
||||||
blitInfo = NULL;
|
blitHeader = NULL;
|
||||||
delete [] pixels;
|
delete [] pixels;
|
||||||
pixels = NULL;
|
pixels = NULL;
|
||||||
}
|
}
|
||||||
@@ -157,6 +204,171 @@ Logical
|
|||||||
return pixels != NULL;
|
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
|
void
|
||||||
MFDSplitView::Repaint()
|
MFDSplitView::Repaint()
|
||||||
{
|
{
|
||||||
|
|||||||
+63
-4
@@ -11,6 +11,16 @@
|
|||||||
// MFDs into the color channels of two video outputs and mounted the map
|
// MFDs into the color channels of two video outputs and mounted the map
|
||||||
// display rotated; on a desktop each display gets its own window instead.
|
// display rotated; on a desktop each display gets its own window instead.
|
||||||
//
|
//
|
||||||
|
// Each window also carries its display's physical button bank, exactly
|
||||||
|
// as mounted in the pod (geometry per vRIO's CockpitLayout):
|
||||||
|
// - MFDs: a 4x2 cluster - 4 red buttons above the glass, 4 below,
|
||||||
|
// RIO addresses descending from the cluster anchor (top-left = hi).
|
||||||
|
// - The map ("secondary screen"): 6 amber buttons down each side -
|
||||||
|
// Secondary 0x10-0x15 on the left, Screen 0x18-0x1D on the right
|
||||||
|
// (the remaining column addresses are Tesla relays, not buttons).
|
||||||
|
// Buttons light from the lamp state the game commands (via PadRIO) and
|
||||||
|
// press/release RIO units with the mouse.
|
||||||
|
//
|
||||||
// SVGA16 fills Pixels() (32bpp top-down, sourceWidth x sourceHeight as
|
// SVGA16 fills Pixels() (32bpp top-down, sourceWidth x sourceHeight as
|
||||||
// passed - already swapped by the caller for the rotated map) and calls
|
// passed - already swapped by the caller for the rotated map) and calls
|
||||||
// Repaint(). Closing a view window just hides it.
|
// Repaint(). Closing a view window just hides it.
|
||||||
@@ -18,14 +28,24 @@
|
|||||||
class MFDSplitView
|
class MFDSplitView
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
enum ButtonStyle
|
||||||
|
{
|
||||||
|
NoButtons = 0,
|
||||||
|
MFDStrips, // 4 above + 4 below, addresses descend from anchorA
|
||||||
|
SideColumns // 6 per side; anchorA = left base, anchorB = right base
|
||||||
|
};
|
||||||
|
|
||||||
MFDSplitView(
|
MFDSplitView(
|
||||||
const char *title,
|
const char *title,
|
||||||
int source_width,
|
int source_width,
|
||||||
int source_height,
|
int source_height,
|
||||||
int client_width,
|
int display_width,
|
||||||
int client_height,
|
int display_height,
|
||||||
int x,
|
int x,
|
||||||
int y
|
int y,
|
||||||
|
ButtonStyle button_style = NoButtons,
|
||||||
|
int anchorA = 0,
|
||||||
|
int anchorB = 0
|
||||||
);
|
);
|
||||||
|
|
||||||
~MFDSplitView();
|
~MFDSplitView();
|
||||||
@@ -40,14 +60,53 @@ public:
|
|||||||
void
|
void
|
||||||
Repaint();
|
Repaint();
|
||||||
|
|
||||||
|
// Window-procedure callbacks (public for the registered WndProc)
|
||||||
|
void
|
||||||
|
Paint();
|
||||||
|
void
|
||||||
|
MouseDown(int x, int y);
|
||||||
|
void
|
||||||
|
MouseUp();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void
|
||||||
|
LayoutButtons(
|
||||||
|
ButtonStyle button_style,
|
||||||
|
int anchorA,
|
||||||
|
int anchorB,
|
||||||
|
int display_width,
|
||||||
|
int display_height,
|
||||||
|
int *client_width,
|
||||||
|
int *client_height
|
||||||
|
);
|
||||||
|
|
||||||
|
struct ScreenButton
|
||||||
|
{
|
||||||
|
int unit;
|
||||||
|
int x, y, w, h;
|
||||||
|
int amber; // 0 = red family, 1 = amber family
|
||||||
|
};
|
||||||
|
|
||||||
|
enum { maxButtons = 20 };
|
||||||
|
|
||||||
void
|
void
|
||||||
*window; // HWND
|
*window; // HWND
|
||||||
void
|
void
|
||||||
*blitInfo; // MFDViewBlit (header + source dims), see .cpp
|
*blitHeader; // BITMAPINFOHEADER for StretchDIBits
|
||||||
unsigned long
|
unsigned long
|
||||||
*pixels;
|
*pixels;
|
||||||
int
|
int
|
||||||
sourceWidth,
|
sourceWidth,
|
||||||
sourceHeight;
|
sourceHeight;
|
||||||
|
|
||||||
|
int
|
||||||
|
displayX, displayY,
|
||||||
|
displayW, displayH;
|
||||||
|
|
||||||
|
ScreenButton
|
||||||
|
buttons[maxButtons];
|
||||||
|
int
|
||||||
|
buttonCount;
|
||||||
|
int
|
||||||
|
pressedIndex;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -87,6 +87,27 @@ namespace
|
|||||||
//############################### PadRIO #################################
|
//############################### PadRIO #################################
|
||||||
//########################################################################
|
//########################################################################
|
||||||
|
|
||||||
|
PadRIO *PadRIO::activeInstance = NULL;
|
||||||
|
|
||||||
|
void
|
||||||
|
PadRIO::SetScreenButton(int unit, Logical pressed)
|
||||||
|
{
|
||||||
|
if (activeInstance != NULL && unit >= 0 && unit < buttonUnits)
|
||||||
|
{
|
||||||
|
activeInstance->screenButton[unit] = pressed ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
PadRIO::GetLampState(int unit)
|
||||||
|
{
|
||||||
|
if (activeInstance != NULL && unit >= 0 && unit < lampCount)
|
||||||
|
{
|
||||||
|
return activeInstance->lampState[unit];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
PadRIO::PadRIO()
|
PadRIO::PadRIO()
|
||||||
{
|
{
|
||||||
Check_Pointer(this);
|
Check_Pointer(this);
|
||||||
@@ -105,6 +126,7 @@ PadRIO::PadRIO()
|
|||||||
|
|
||||||
memset(buttonDown, 0, sizeof(buttonDown));
|
memset(buttonDown, 0, sizeof(buttonDown));
|
||||||
memset(lampState, 0, sizeof(lampState));
|
memset(lampState, 0, sizeof(lampState));
|
||||||
|
memset(screenButton, 0, sizeof(screenButton));
|
||||||
|
|
||||||
invertX = False;
|
invertX = False;
|
||||||
invertY = False;
|
invertY = False;
|
||||||
@@ -125,12 +147,18 @@ PadRIO::PadRIO()
|
|||||||
MajorRevision = 4;
|
MajorRevision = 4;
|
||||||
MinorRevision = 2;
|
MinorRevision = 2;
|
||||||
|
|
||||||
|
activeInstance = this;
|
||||||
|
|
||||||
DEBUG_STREAM << "PadRIO: virtual RIO active (XInput pad + keyboard)\n" << std::flush;
|
DEBUG_STREAM << "PadRIO: virtual RIO active (XInput pad + keyboard)\n" << std::flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
PadRIO::~PadRIO()
|
PadRIO::~PadRIO()
|
||||||
{
|
{
|
||||||
Check_Pointer(this);
|
Check_Pointer(this);
|
||||||
|
if (activeInstance == this)
|
||||||
|
{
|
||||||
|
activeInstance = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logical
|
Logical
|
||||||
@@ -290,6 +318,13 @@ void
|
|||||||
desired[keyboardButtonMap[i].rioUnit] = 1;
|
desired[keyboardButtonMap[i].rioUnit] = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (int i = 0; i < buttonUnits; ++i)
|
||||||
|
{
|
||||||
|
if (screenButton[i])
|
||||||
|
{
|
||||||
|
desired[i] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int unit = 0; unit < buttonUnits; ++unit)
|
for (int unit = 0; unit < buttonUnits; ++unit)
|
||||||
{
|
{
|
||||||
|
|||||||
+21
-1
@@ -50,11 +50,24 @@ public:
|
|||||||
SetLamp(int lampNumber, int state);
|
SetLamp(int lampNumber, int state);
|
||||||
|
|
||||||
// Lamp state the game has commanded, indexed by RIO lamp number.
|
// Lamp state the game has commanded, indexed by RIO lamp number.
|
||||||
// A future on-screen panel reads this to light its buttons.
|
// The on-screen cockpit buttons read this to light themselves.
|
||||||
enum { lampCount = 64 };
|
enum { lampCount = 64 };
|
||||||
unsigned char
|
unsigned char
|
||||||
lampState[lampCount];
|
lampState[lampCount];
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// On-screen cockpit buttons (MFDSplitView strips) press RIO units
|
||||||
|
// through these; they are no-ops when no PadRIO is active (e.g.
|
||||||
|
// real serial hardware selected).
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
static void
|
||||||
|
SetScreenButton(int unit, Logical pressed);
|
||||||
|
static int
|
||||||
|
GetLampState(int unit);
|
||||||
|
static Logical
|
||||||
|
IsActive()
|
||||||
|
{ return activeInstance != NULL; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void
|
void
|
||||||
PollInputs();
|
PollInputs();
|
||||||
@@ -69,6 +82,13 @@ protected:
|
|||||||
int
|
int
|
||||||
queueHead, queueTail;
|
queueHead, queueTail;
|
||||||
|
|
||||||
|
static PadRIO
|
||||||
|
*activeInstance;
|
||||||
|
|
||||||
|
// pressed state driven by the on-screen cockpit buttons
|
||||||
|
unsigned char
|
||||||
|
screenButton[0x48];
|
||||||
|
|
||||||
unsigned long
|
unsigned long
|
||||||
lastPollTick,
|
lastPollTick,
|
||||||
lastPadCheckTick;
|
lastPadCheckTick;
|
||||||
|
|||||||
+28
-8
@@ -3859,30 +3859,50 @@ SVGA16::SVGA16(
|
|||||||
int frame_h = GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYFIXEDFRAME);
|
int frame_h = GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYFIXEDFRAME);
|
||||||
int frame_w = 2 * GetSystemMetrics(SM_CXFIXEDFRAME);
|
int frame_w = 2 * GetSystemMetrics(SM_CXFIXEDFRAME);
|
||||||
int grid_x = init_width + frame_w;
|
int grid_x = init_width + frame_w;
|
||||||
int row2_y = cell_h + frame_h;
|
|
||||||
|
|
||||||
|
// the MFD button strips grow each window (mirror of
|
||||||
|
// MFDSplitView::LayoutButtons so the grid rows still tile)
|
||||||
|
int strip_h = cell_h / 8;
|
||||||
|
if (strip_h < 18) strip_h = 18;
|
||||||
|
if (strip_h > 40) strip_h = 40;
|
||||||
|
int mfd_extra_h = 2 * (strip_h + 8);
|
||||||
|
int row2_y = cell_h + mfd_extra_h + frame_h;
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Button banks per display (addresses per vRIO CockpitLayout,
|
||||||
|
// bank-to-display placement per the pod cockpit):
|
||||||
|
// upper left MFD 0x2F.. / upper center 0x27.. / upper right
|
||||||
|
// 0x37.. / lower left 0x0F.. / lower right 0x07.. and the map
|
||||||
|
// flanked by Secondary 0x10-0x15 / Screen 0x18-0x1D.
|
||||||
|
//---------------------------------------------------------------
|
||||||
splitView[SplitMFDUpperLeft] = new MFDSplitView(
|
splitView[SplitMFDUpperLeft] = new MFDSplitView(
|
||||||
"MFD upper left", init_width, init_height,
|
"MFD upper left", init_width, init_height,
|
||||||
cell_w, cell_h, grid_x, 0);
|
cell_w, cell_h, grid_x, 0,
|
||||||
|
MFDSplitView::MFDStrips, 0x2F);
|
||||||
splitView[SplitMFDUpperCenter] = new MFDSplitView(
|
splitView[SplitMFDUpperCenter] = new MFDSplitView(
|
||||||
"MFD upper center", init_width, init_height,
|
"MFD upper center", init_width, init_height,
|
||||||
cell_w, cell_h, grid_x + (cell_w + frame_w), 0);
|
cell_w, cell_h, grid_x + (cell_w + frame_w), 0,
|
||||||
|
MFDSplitView::MFDStrips, 0x27);
|
||||||
splitView[SplitMFDUpperRight] = new MFDSplitView(
|
splitView[SplitMFDUpperRight] = new MFDSplitView(
|
||||||
"MFD upper right", init_width, init_height,
|
"MFD upper right", init_width, init_height,
|
||||||
cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), 0);
|
cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), 0,
|
||||||
|
MFDSplitView::MFDStrips, 0x37);
|
||||||
splitView[SplitMFDLowerLeft] = new MFDSplitView(
|
splitView[SplitMFDLowerLeft] = new MFDSplitView(
|
||||||
"MFD lower left", init_width, init_height,
|
"MFD lower left", init_width, init_height,
|
||||||
cell_w, cell_h, grid_x, row2_y);
|
cell_w, cell_h, grid_x, row2_y,
|
||||||
|
MFDSplitView::MFDStrips, 0x0F);
|
||||||
splitView[SplitMFDLowerRight] = new MFDSplitView(
|
splitView[SplitMFDLowerRight] = new MFDSplitView(
|
||||||
"MFD lower right", init_width, init_height,
|
"MFD lower right", init_width, init_height,
|
||||||
cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), row2_y);
|
cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), row2_y,
|
||||||
|
MFDSplitView::MFDStrips, 0x07);
|
||||||
// map is portrait: source rotated 90 degrees clockwise
|
// map is portrait: source rotated 90 degrees clockwise
|
||||||
splitView[SplitMap] = new MFDSplitView(
|
splitView[SplitMap] = new MFDSplitView(
|
||||||
"Map", init_height, init_width,
|
"Map", init_height, init_width,
|
||||||
cell_h, cell_w,
|
cell_h, cell_w,
|
||||||
grid_x + (cell_w + frame_w) + (cell_w - cell_h) / 2, row2_y);
|
grid_x + (cell_w + frame_w) + (cell_w - cell_h) / 2, row2_y,
|
||||||
|
MFDSplitView::SideColumns, 0x10, 0x18);
|
||||||
|
|
||||||
DEBUG_STREAM << "SVGA16: split views active - 5 MFD windows + rotated map (scale "
|
DEBUG_STREAM << "SVGA16: split views active - 5 MFD windows + rotated map, cockpit buttons on (scale "
|
||||||
<< scale_percent << "%)\n" << std::flush;
|
<< scale_percent << "%)\n" << std::flush;
|
||||||
}
|
}
|
||||||
//STUBBED: VIDEO RB 1/15/07
|
//STUBBED: VIDEO RB 1/15/07
|
||||||
|
|||||||
+14
-4
@@ -56,10 +56,20 @@ so press-feedback works like the real button field.
|
|||||||
90°-rotated map), rendered CPU-side from the shared gauge canvas — the
|
90°-rotated map), rendered CPU-side from the shared gauge canvas — the
|
||||||
external BitBlt-mirror wrapper is obsolete. Verified visually (MFD score
|
external BitBlt-mirror wrapper is obsolete. Verified visually (MFD score
|
||||||
readout, full tactical map).
|
readout, full tactical map).
|
||||||
- Next in A: on-screen RIO panel fed by `PadRIO::lampState[]` (the
|
- ✅ **Cockpit buttons on the displays** (2026-07-12): each split window
|
||||||
cockpit-feel layout — vRIO button clusters around the displays); port the
|
carries its physical button bank — 4+4 red buttons per MFD, 6 amber per
|
||||||
full vRIO bindings-file model (deflect/rate/deadzone per axis, rebinding);
|
map side (Secondary/Screen columns; addresses per vRIO `CockpitLayout`,
|
||||||
pilot keypad (numpad → KeyEvent); Steam Input once C starts.
|
placement per the pod). Mouse presses inject into `PadRIO`; the game's
|
||||||
|
lamp commands light them (verified: PRESET lamps lit on the map flank,
|
||||||
|
aligned with the glass's own edge labels).
|
||||||
|
- Next in A: port the full vRIO bindings-file model (deflect/rate/deadzone
|
||||||
|
per axis, rebinding); pilot keypad (numpad / on-screen 4×4 → KeyEvent);
|
||||||
|
Steam Input once C starts.
|
||||||
|
- Polish pass (queued): **vRIO's Dynamic Lighting RGB-keyboard mirror** —
|
||||||
|
lamp states glow on the physical keyboard's keys (per-key or zone-lit),
|
||||||
|
blinking with the flash modes. Needs package identity for background
|
||||||
|
light control (vRIO's `pkg\Register-vRIO.ps1` pattern; see the vRIO
|
||||||
|
README for the Settings → Dynamic Lighting steps).
|
||||||
- vRIO itself stays useful as a dev harness against unmodified builds.
|
- vRIO itself stays useful as a dev harness against unmodified builds.
|
||||||
|
|
||||||
## Workstream B — In-game sessions (from TeslaConsole)
|
## Workstream B — In-game sessions (from TeslaConsole)
|
||||||
|
|||||||
@@ -98,6 +98,9 @@ Controls (XInput controller and/or keyboard):
|
|||||||
The full pod cockpit comes up as separate windows: the main view, five
|
The full pod cockpit comes up as separate windows: the main view, five
|
||||||
green MFD screens, the portrait map, and the orange plasma glass below
|
green MFD screens, the portrait map, and the orange plasma glass below
|
||||||
the main view. Drag them wherever you like; closing one just hides it.
|
the main view. Drag them wherever you like; closing one just hides it.
|
||||||
|
The red buttons around each MFD and the amber buttons flanking the map
|
||||||
|
are the pod's real button banks: click them with the mouse, and they
|
||||||
|
light up as the game commands their lamps.
|
||||||
environ.ini options: L4PADFLIP=XY inverts the stick axes, L4MFDSCALE=n
|
environ.ini options: L4PADFLIP=XY inverts the stick axes, L4MFDSCALE=n
|
||||||
sizes the MFD/map windows (percent, default 50), L4PLASMASCALE=n sets
|
sizes the MFD/map windows (percent, default 50), L4PLASMASCALE=n sets
|
||||||
the plasma pixel size (default 4), L4PLASMAPOS=x,y places the plasma.
|
the plasma pixel size (default 4), L4PLASMAPOS=x,y places the plasma.
|
||||||
|
|||||||
Reference in New Issue
Block a user