Panel: the vRIO layout + coloration, plasma flip fix, button-trap guard
Per Cyd: the on-screen panel now mirrors vRIO (C:\VWE\vrio -- CockpitLayout.cs
+ PanelCanvas.cs, itself the RIOJoy profile-editor layout from the original
Win32 RIO mockup): five 4x2 MFD clusters (addresses descending), four 1x8
board columns (Throttle/Secondary/Screen/Joystick), the two 4x4 hex keypads
(0x50/0x60 -- cells emit real keypad KeyEvents: internal=pilot unit, external=
operator unit), physical names (Panic/Main/Hat*/Pinky...), vRIO colors: red
lamp cells + yellow Secondary/Screen + neutral-blue keypads, flash half-
periods 500/250/125ms, right-click latch (gold outline). 104 controls.
PlasmaWindow: the gauge renderer writes the 128x32 buffer TOP-DOWN -- the
bottom-up flip rendered the marquee upside down (user-reported); copy straight.
Button-trap guard (the e2c21c4 pattern): the authentic base
ConfigureMappableMessageHandler FAIL trap is abort() under DEBUGOFF -- any
aux/zoom button without its L4 override reconstructed KILLED the game on
press. Default now: loud [FAIL] log + ignore; BT_BUTTON_TRAP=1 restores the
hard trap. Verified live: synthetic click on a Secondary cell -> 2 [FAIL]
lines (press+release through panel->PadRIO->ProcessRIOEvent->streamed
mapping), game survives and keeps ticking.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+362
-133
@@ -4,7 +4,19 @@
|
||||
//###########################################################################
|
||||
// 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.
|
||||
//
|
||||
// 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"
|
||||
@@ -12,141 +24,214 @@
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
//###########################################################################
|
||||
// Layout: rows of banks, each button carries its RIO address + label.
|
||||
// The vRIO cockpit layout (CockpitLayout.cs transcribed)
|
||||
//###########################################################################
|
||||
|
||||
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,
|
||||
CellW = 66,
|
||||
CellH = 34,
|
||||
PanelMargin = 6,
|
||||
FirstRow = 1, // the shared layout leaves grid row 0 empty
|
||||
RepaintTimerId = 1,
|
||||
RepaintMilliseconds = 100
|
||||
};
|
||||
|
||||
static HWND panelWindow = NULL;
|
||||
static int pressedAddress = -1;
|
||||
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 },
|
||||
{ "Throttle", KindColumn, 1, 8, 1, 0, 9, 0x38 },
|
||||
{ "Secondary", KindColumn, 1, 8, 1, 4, 9, 0x10 },
|
||||
{ "Screen", KindColumn, 1, 8, 1, 6, 9, 0x18 },
|
||||
{ "Joystick", KindColumn, 1, 8, 1, 11, 9, 0x40 },
|
||||
{ "Internal Keypad", KindKeypad, 4, 4, 0, 4, 4, 0x50 },
|
||||
{ "External Keypad", KindKeypad, 4, 4, 0, 7, 9, 0x60 },
|
||||
};
|
||||
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 void
|
||||
LayoutButtons()
|
||||
static RECT
|
||||
CellRect(int col, int row)
|
||||
{
|
||||
int y = PanelMargin;
|
||||
for (int r = 0; r < PanelRowCount; ++r)
|
||||
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)
|
||||
{
|
||||
int x = PanelMargin;
|
||||
for (int c = 0; c < panelRows[r].count; ++c)
|
||||
const PanelGroupDef &group = panelGroups[g];
|
||||
int count = group.cols * group.rows;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
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)
|
||||
int address;
|
||||
switch (group.kind)
|
||||
{
|
||||
x += BankGap;
|
||||
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);
|
||||
}
|
||||
y += ButtonHeight + ButtonGap + 4;
|
||||
}
|
||||
}
|
||||
|
||||
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 < PanelButtonCount; ++i)
|
||||
for (int i = 0; i < panelCellCount; ++i)
|
||||
{
|
||||
const RECT &r = panelButtons[i].rect;
|
||||
const RECT &r = panelCells[i].rect;
|
||||
if (x >= r.left && x < r.right && y >= r.top && y < r.bottom)
|
||||
{
|
||||
return panelButtons[i].address;
|
||||
return panelCells[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)
|
||||
//###########################################################################
|
||||
// Lamp decode (RIOBase::LampState): flash = low 2 bits (solid/slow/med/
|
||||
// fast), filament brightness = max of the state1 (>>2) and state2 (>>4)
|
||||
// 2-bit fields (0 off / 1 dim / 3 bright). Flash blinks between the
|
||||
// commanded brightness and off, half-periods 500/250/125 ms (vRIO).
|
||||
//###########################################################################
|
||||
|
||||
static int
|
||||
LampBrightnessOf(int state, unsigned long tick)
|
||||
{
|
||||
int level1 = (state >> 2) & 0x3; // 0/1/3
|
||||
int level1 = (state >> 2) & 0x3;
|
||||
int level2 = (state >> 4) & 0x3;
|
||||
int level = (level1 > level2) ? level1 : level2;
|
||||
if (pressed)
|
||||
if (level == 0)
|
||||
{
|
||||
return RGB(255, 255, 160);
|
||||
return 0;
|
||||
}
|
||||
switch (level)
|
||||
int flash = state & 0x3;
|
||||
if (flash != 0)
|
||||
{
|
||||
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
|
||||
unsigned long half_period =
|
||||
(flash == 1) ? 500 : (flash == 2) ? 250 : 125;
|
||||
if ((tick / half_period) & 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// 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
|
||||
@@ -154,46 +239,148 @@ static void
|
||||
{
|
||||
PAINTSTRUCT paint;
|
||||
HDC dc = BeginPaint(window, &paint);
|
||||
unsigned long tick = GetTickCount();
|
||||
|
||||
RECT client;
|
||||
GetClientRect(window, &client);
|
||||
HBRUSH background = CreateSolidBrush(RGB(24, 26, 28));
|
||||
HBRUSH background = CreateSolidBrush(RGB(28, 28, 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)
|
||||
//
|
||||
// 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 PanelButton &button = panelButtons[i];
|
||||
int lamp = PadRIO::GetLampState(button.address);
|
||||
int pressed = (button.address == pressedAddress);
|
||||
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);
|
||||
|
||||
HBRUSH face = CreateSolidBrush(LampColor(lamp, pressed));
|
||||
FillRect(dc, &button.rect, face);
|
||||
DeleteObject(face);
|
||||
FrameRect(dc, &button.rect,
|
||||
(HBRUSH)GetStockObject(pressed ? WHITE_BRUSH : GRAY_BRUSH));
|
||||
//
|
||||
// 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, "Secondary") == 0 ||
|
||||
strcmp(cell.group->title, "Screen") == 0);
|
||||
|
||||
SetTextColor(dc, RGB(230, 230, 230));
|
||||
WCHAR label[16];
|
||||
int n = 0;
|
||||
for (const char *s = button.label; *s && n < 15; ++s)
|
||||
//
|
||||
// Lamp shade: a locally held cap floods bright; else the host-
|
||||
// commanded lamp state (with flash phase) decides.
|
||||
//
|
||||
int shade = 0;
|
||||
if (lamp_capable)
|
||||
{
|
||||
label[n++] = (WCHAR)*s;
|
||||
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));
|
||||
}
|
||||
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)
|
||||
{
|
||||
@@ -216,9 +403,18 @@ static LRESULT CALLBACK
|
||||
(int)(short)HIWORD(lparam));
|
||||
if (address >= 0)
|
||||
{
|
||||
pressedAddress = address;
|
||||
SetCapture(window);
|
||||
PadRIO::SetScreenButton(address, 1);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -234,6 +430,28 @@ static LRESULT CALLBACK
|
||||
}
|
||||
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
|
||||
@@ -256,22 +474,30 @@ void
|
||||
return;
|
||||
}
|
||||
|
||||
LayoutButtons();
|
||||
LayoutCells();
|
||||
memset(latched, 0, sizeof(latched));
|
||||
|
||||
//
|
||||
// Window size from the widest row (row 0: 24 buttons + 2 bank gaps).
|
||||
//
|
||||
int client_width = 0;
|
||||
for (int i = 0; i < PanelButtonCount; ++i)
|
||||
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 (panelButtons[i].rect.right > client_width)
|
||||
if (panelCells[i].rect.right > client_width)
|
||||
{
|
||||
client_width = panelButtons[i].rect.right;
|
||||
client_width = panelCells[i].rect.right;
|
||||
}
|
||||
if (panelCells[i].rect.bottom > client_height)
|
||||
{
|
||||
client_height = panelCells[i].rect.bottom;
|
||||
}
|
||||
}
|
||||
client_width += PanelMargin;
|
||||
int client_height = panelButtons[PanelButtonCount-1].rect.bottom
|
||||
+ PanelMargin;
|
||||
client_height += PanelMargin;
|
||||
|
||||
WNDCLASSW window_class;
|
||||
memset(&window_class, 0, sizeof(window_class));
|
||||
@@ -299,7 +525,7 @@ void
|
||||
SetTimer(panelWindow, RepaintTimerId, RepaintMilliseconds, NULL);
|
||||
ShowWindow(panelWindow, SW_SHOWNOACTIVATE);
|
||||
DEBUG_STREAM << "[padpanel] cockpit button panel up ("
|
||||
<< PanelButtonCount << " buttons)\n" << std::flush;
|
||||
<< panelCellCount << " controls, vRIO layout)\n" << std::flush;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -311,4 +537,7 @@ void
|
||||
DestroyWindow(panelWindow);
|
||||
panelWindow = NULL;
|
||||
}
|
||||
if (titleFont) { DeleteObject(titleFont); titleFont = NULL; }
|
||||
if (buttonFont) { DeleteObject(buttonFont); buttonFont = NULL; }
|
||||
if (subFont) { DeleteObject(subFont); subFont = NULL; }
|
||||
}
|
||||
|
||||
@@ -496,6 +496,20 @@ void
|
||||
{
|
||||
return;
|
||||
}
|
||||
//
|
||||
// The keypad address space (0x50-0x6F, the vRIO panel's two 4x4 hex
|
||||
// keypads): press emits a keypad KeyEvent -- internal (0x50) on the
|
||||
// pilot unit, external (0x60) on the operator unit; keys have no
|
||||
// release event.
|
||||
//
|
||||
if (unit >= 0x50 && unit <= 0x6F)
|
||||
{
|
||||
if (pressed)
|
||||
{
|
||||
activeInstance->EmitKeypad((unit >= 0x60) ? 1 : 0, unit & 0x0F);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (unit < 0 || unit >= LBE4ControlsManager::ButtonCount)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -113,13 +113,13 @@ Logical
|
||||
//
|
||||
// 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.
|
||||
// gauge renderer writes the buffer TOP-DOWN (user-verified 2026-07-17:
|
||||
// a bottom-up flip rendered the marquee upside down) -- copy straight.
|
||||
//
|
||||
const Byte *source = pixelBuffer->Data.MapPointer;
|
||||
for (int y = 0; y < plasmaHeight; ++y)
|
||||
{
|
||||
const Byte *line = source + (plasmaHeight - 1 - y) * plasmaWidth;
|
||||
const Byte *line = source + y * plasmaWidth;
|
||||
unsigned long *destination = blitBuffer + y * plasmaWidth;
|
||||
for (int x = 0; x < plasmaWidth; ++x)
|
||||
{
|
||||
|
||||
@@ -441,7 +441,21 @@ void
|
||||
{
|
||||
DEBUG_STREAM << "[FAIL] Unhandled button mapping (base ConfigureMappableMessageHandler)"
|
||||
<< std::endl;
|
||||
Fail("Unhandled button mapping!\n"); // MECHMPPR.CPP:0x7a
|
||||
//
|
||||
// Tester guard (the e2c21c4 '&'-gate pattern): under DEBUGOFF Fail()
|
||||
// is abort() -- ANY aux/zoom button whose L4 override is not yet
|
||||
// reconstructed KILLED the game on press, and the glass panel / PAD
|
||||
// bindings make every button one click away. Default: the loud log
|
||||
// line above + ignore. BT_BUTTON_TRAP=1 restores the authentic hard
|
||||
// trap (the binary's own base body: push "Unhandled button mapping!"
|
||||
// + call Fail).
|
||||
//
|
||||
static const int s_trap =
|
||||
(getenv("BT_BUTTON_TRAP") != 0 && *getenv("BT_BUTTON_TRAP") != '0');
|
||||
if (s_trap)
|
||||
{
|
||||
Fail("Unhandled button mapping!\n"); // MECHMPPR.CPP:0x7a
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Reference in New Issue
Block a user