Two glass-panel indicator fixes reported by playtesters. 1. FLASH SLOW UNLESS FOCUSED. The per-display panel windows repaint off a 62ms WM_TIMER, and Windows coalesces/throttles timer + paint messages for a window that is in the BACKGROUND (unfocused) -- so with the game window holding focus the panels' lamp flash (a repaint-driven animation) crawled, and giving a panel focus un-throttled it. (My earlier surround fix drew in the main D3D frame and never touched these separate windows.) New BTGlassPanels_Tick(), called once per frame from the main render loop (L4VIDEO), drives a synchronous InvalidateRect+UpdateWindow at the ~16Hz flash cadence -- the main loop runs every frame regardless of which window has focus, and the forced paint bypasses the throttled timer-message path. The WM_TIMER stays for its one-shot re-snap. 2. RED PANIC/EJECT. 0x3D is the Panic/Eject button; on the blue flight panel it was just another blue block. The shared flight-grid geometry (L4RIOBANK) now tags 0x3D as colorClass 0 (red); L4GLASSWIN's AdoptBank maps colorClass 0 -> ClrRed explicitly (the flight bank's ClrBlue default no longer swallows it), and the surround (L4VB16) already renders non-blue/yellow as red -- so the eject stands out red in both the surround and the exploded window. Verified: Release links clean (40 tolerated /FORCE externals only); exploded panels boot + run 13s no crash under the per-frame repaint pump; riobank dump confirms 0x3d class=0 (red), neighbours class=2 (blue). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
300 lines
10 KiB
C++
300 lines
10 KiB
C++
#include "mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
//###########################################################################
|
|
// L4RIOBANK -- the ONE button-bank geometry for a pod display. Design +
|
|
// the under-glass rule: l4riobank.h. Consumed by the cockpit surround
|
|
// (L4VB16.cpp, canvas space) and the exploded per-display windows
|
|
// (L4GLASSWIN.cpp, client space); neither owns a copy any more.
|
|
//###########################################################################
|
|
|
|
#include "l4riobank.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
namespace
|
|
{
|
|
//-------------------------------------------------------------------
|
|
// The lamp strip at NATIVE glass size, scaled down with the display
|
|
// but floored so a half-scale surround lamp is still a bar and not a
|
|
// smudge. RP412 L4MFDVIEW works at native only and hardcodes 10.
|
|
//-------------------------------------------------------------------
|
|
const int nativeShortAxis = 480; // MFD 640x480 / map 480x640
|
|
const int nativeStrip = 10;
|
|
const int minimumStrip = 6;
|
|
const int defaultGap = 4;
|
|
|
|
//-------------------------------------------------------------------
|
|
// Per-column horizontal nudge for the 4 MFD buttons, left->right, so
|
|
// they line up with the (unevenly spaced) DISPLAY / PROGRAM / ...
|
|
// legends painted in the imagery. Measured against the native 640
|
|
// glass in L4GLASSWIN (RedOffsetX + kRedColDX) and scaled here, so
|
|
// both renderers inherit the same alignment. Tunable.
|
|
//-------------------------------------------------------------------
|
|
const int nativeMfdW = 640;
|
|
const int mfdColumnDX[4] = { 0, 1, 5, 5 };
|
|
|
|
//-------------------------------------------------------------------
|
|
// The map paints its own legend beside each side button (the six
|
|
// cells: MAP+, MAP-, IR, crouch, searchlight, display-mode), and the
|
|
// buttons have to line up with THAT grid, not with height/6.
|
|
//
|
|
// MEASURED off a native 480x640 capture (scratchpad/measurelegend.py
|
|
// over an exploded Secondary/Radar window): the first cell border sits
|
|
// 3 rows down and the six cells are 102 tall on a 107 pitch. RP412's
|
|
// map measures 13 + 6x102 on a 105 pitch -- same cell height, different
|
|
// top and pitch, so its numbers do NOT transfer; these are ours.
|
|
//
|
|
// Our previous even division (displayH/6 + 1 = 107) had the pitch right
|
|
// by luck and sat 3px high of the labels.
|
|
//-------------------------------------------------------------------
|
|
const int mapLegendSpan = 640;
|
|
const int mapLegendTop = 3;
|
|
const int mapLegendCell = 102;
|
|
const int mapLegendPitch = 107;
|
|
|
|
int Scaled(int nativeValue, int actual, int nativeExtent)
|
|
{
|
|
if (nativeExtent <= 0) return nativeValue;
|
|
return (nativeValue * actual) / nativeExtent;
|
|
}
|
|
|
|
void Push(
|
|
BTRioBank *bank,
|
|
int address, int x, int y, int w, int h,
|
|
int colorClass, int inert, const char *label
|
|
)
|
|
{
|
|
if (bank->buttonCount >= BTRioBankMaxButtons) return;
|
|
if (w <= 0 || h <= 0) return;
|
|
BTRioButton *button = &bank->buttons[bank->buttonCount++];
|
|
button->address = address;
|
|
button->x = x;
|
|
button->y = y;
|
|
button->w = w;
|
|
button->h = h;
|
|
button->colorClass = colorClass;
|
|
button->inert = inert;
|
|
button->label = label;
|
|
}
|
|
|
|
void GrowBounds(BTRioBank *bank, int x, int y, int w, int h)
|
|
{
|
|
if (bank->boundsW == 0 && bank->boundsH == 0)
|
|
{
|
|
bank->boundsX = x; bank->boundsY = y;
|
|
bank->boundsW = w; bank->boundsH = h;
|
|
return;
|
|
}
|
|
int left = (x < bank->boundsX) ? x : bank->boundsX;
|
|
int top = (y < bank->boundsY) ? y : bank->boundsY;
|
|
int right = bank->boundsX + bank->boundsW;
|
|
int bottom = bank->boundsY + bank->boundsH;
|
|
if (x + w > right) right = x + w;
|
|
if (y + h > bottom) bottom = y + h;
|
|
bank->boundsX = left;
|
|
bank->boundsY = top;
|
|
bank->boundsW = right - left;
|
|
bank->boundsH = bottom - top;
|
|
}
|
|
}
|
|
|
|
//###########################################################################
|
|
|
|
void
|
|
BTRioBankMetricsFor(int displayW, int displayH, BTRioBankMetrics *out)
|
|
{
|
|
if (out == 0) return;
|
|
|
|
int shortAxis = (displayW < displayH) ? displayW : displayH;
|
|
|
|
int strip = Scaled(nativeStrip, shortAxis, nativeShortAxis);
|
|
if (strip < minimumStrip) strip = minimumStrip;
|
|
|
|
out->strip = strip;
|
|
out->gap = defaultGap;
|
|
|
|
// The cells with no glass behind them (the flight bank, the map's foot
|
|
// row) are drawn in full, so they get a plain readable size scaled off
|
|
// the same axis rather than a derived depth.
|
|
out->cellW = Scaled(58, shortAxis, nativeShortAxis);
|
|
out->cellH = Scaled(28, shortAxis, nativeShortAxis);
|
|
if (out->cellW < 12) out->cellW = 12;
|
|
if (out->cellH < 12) out->cellH = 12;
|
|
}
|
|
|
|
//###########################################################################
|
|
|
|
void
|
|
BTRioBankLayout(
|
|
BTRioBankStyle style,
|
|
int originX,
|
|
int originY,
|
|
int displayW,
|
|
int displayH,
|
|
int anchorA,
|
|
int anchorB,
|
|
const BTRioBankMetrics *metrics,
|
|
const int *bottomAddrs,
|
|
BTRioBank *out
|
|
)
|
|
{
|
|
if (out == 0 || metrics == 0) return;
|
|
memset(out, 0, sizeof(*out));
|
|
|
|
const int strip = metrics->strip;
|
|
const int gap = metrics->gap;
|
|
|
|
// the glass itself is always inside the bounds
|
|
GrowBounds(out, originX, originY, displayW, displayH);
|
|
|
|
if (style == BTRioBankMfd)
|
|
{
|
|
//---------------------------------------------------------------
|
|
// 4 above the glass, 4 below. Addresses descend from the anchor
|
|
// row-major (vRIO CockpitLayout::Mfd): top = anchorA-i, bottom =
|
|
// anchorA-4-i. Each button reaches HALF THE GLASS in and clears
|
|
// the edge by `strip`, so the two banks meet in the middle and
|
|
// every pixel of the display is some button's press target.
|
|
//---------------------------------------------------------------
|
|
int depth = displayH / 2;
|
|
if (depth < strip) depth = strip;
|
|
|
|
int slotW = displayW / 4;
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
int dx = Scaled(mfdColumnDX[i], displayW, nativeMfdW);
|
|
int x = originX + i * slotW + dx;
|
|
int w = slotW - gap;
|
|
|
|
int topY = originY - strip;
|
|
int botY = originY + displayH - depth;
|
|
Push(out, anchorA - i, x, topY, w, depth + strip, 0, 0, 0);
|
|
Push(out, anchorA - 4 - i, x, botY, w, depth + strip, 0, 0, 0);
|
|
GrowBounds(out, x, topY, w, depth + strip);
|
|
GrowBounds(out, x, botY, w, depth + strip);
|
|
}
|
|
}
|
|
else if (style == BTRioBankRadar)
|
|
{
|
|
//---------------------------------------------------------------
|
|
// The portrait map: 6 buttons down each side (anchorA left,
|
|
// anchorB right) plus an optional 4 along the foot. Same rule
|
|
// turned on its side -- each column reaches half the map in and
|
|
// leaves `strip` clearing the edge.
|
|
//
|
|
// The FOOT ROW IS PUSHED FIRST, deliberately: the columns now
|
|
// reach half the width each, so they cover the foot band too,
|
|
// and the hit test takes the first match. Foot-first keeps
|
|
// those four addresses reachable.
|
|
//---------------------------------------------------------------
|
|
int depth = displayW / 2;
|
|
if (depth < strip) depth = strip;
|
|
|
|
if (bottomAddrs != 0)
|
|
{
|
|
int bandH = metrics->cellH;
|
|
int bandY = originY + displayH - bandH;
|
|
int bandW = displayW / 4;
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
int x = originX + i * bandW;
|
|
Push(out, bottomAddrs[i], x, bandY, bandW - gap,
|
|
bandH + strip, 1, 0, 0);
|
|
GrowBounds(out, x, bandY, bandW - gap, bandH + strip);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// Six cells down the height, on the map's MEASURED legend grid
|
|
// (see mapLegend* above). Top and bottom are scaled SEPARATELY
|
|
// and subtracted rather than scaling a height directly: rounding
|
|
// a height would let the buttons drift out of step with the
|
|
// labels a display or two down the column.
|
|
//---------------------------------------------------------------
|
|
int leftX = originX - strip;
|
|
int rightX = originX + displayW - depth;
|
|
for (int i = 0; i < 6; ++i)
|
|
{
|
|
int cellTop = mapLegendTop + i * mapLegendPitch;
|
|
int top = (cellTop * displayH) / mapLegendSpan;
|
|
int bottom = ((cellTop + mapLegendCell) * displayH) / mapLegendSpan;
|
|
int h = bottom - top;
|
|
if (h < 1) h = 1;
|
|
int y = originY + top;
|
|
Push(out, anchorA + i, leftX, y, depth + strip, h, 1, 0, 0);
|
|
Push(out, anchorB + i, rightX, y, depth + strip, h, 1, 0, 0);
|
|
GrowBounds(out, leftX, y, depth + strip, h);
|
|
GrowBounds(out, rightX, y, depth + strip, h);
|
|
}
|
|
}
|
|
}
|
|
|
|
//###########################################################################
|
|
|
|
void
|
|
BTRioBankFlightGrid(
|
|
int originX,
|
|
int originY,
|
|
int baseAddress,
|
|
int count,
|
|
int columns,
|
|
const char *const *labels,
|
|
const int *inert,
|
|
const BTRioBankMetrics *metrics,
|
|
BTRioBank *out
|
|
)
|
|
{
|
|
if (out == 0 || metrics == 0 || columns <= 0) return;
|
|
|
|
const int cw = metrics->cellW;
|
|
const int ch = metrics->cellH;
|
|
const int gap = metrics->gap;
|
|
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
int column = i % columns;
|
|
int row = i / columns;
|
|
int x = originX + column * (cw + gap);
|
|
int y = originY + row * (ch + gap);
|
|
// Flight blocks are blue (colorClass 2), EXCEPT the Panic/Eject at 0x3D:
|
|
// red (colorClass 0) so the eject stands out on the blue panel.
|
|
int cc = (baseAddress + i == 0x3D) ? 0 : 2;
|
|
Push(out, baseAddress + i, x, y, cw, ch, cc,
|
|
(inert != 0) ? inert[i] : 0,
|
|
(labels != 0) ? labels[i] : 0);
|
|
GrowBounds(out, x, y, cw, ch);
|
|
}
|
|
}
|
|
|
|
//###########################################################################
|
|
|
|
void
|
|
BTRioBankDump(const char *tag, const BTRioBank *bank)
|
|
{
|
|
static int enabled = -1;
|
|
static int printed = 0;
|
|
|
|
if (enabled < 0)
|
|
{
|
|
const char *e = getenv("BT_RIOBANK_LOG");
|
|
enabled = (e != 0 && e[0] != '0') ? 1 : 0;
|
|
}
|
|
if (!enabled || bank == 0 || printed >= 16) return;
|
|
++printed;
|
|
|
|
DEBUG_STREAM << "[riobank] " << (tag ? tag : "?")
|
|
<< " bounds=(" << bank->boundsX << "," << bank->boundsY << ","
|
|
<< bank->boundsW << "," << bank->boundsH << ") "
|
|
<< bank->buttonCount << " buttons\n";
|
|
for (int i = 0; i < bank->buttonCount; ++i)
|
|
{
|
|
const BTRioButton &b = bank->buttons[i];
|
|
DEBUG_STREAM << "[riobank] addr=0x" << std::hex << b.address << std::dec
|
|
<< " rect=(" << b.x << "," << b.y << "," << b.w << "," << b.h << ")"
|
|
<< " class=" << b.colorClass << "\n";
|
|
}
|
|
DEBUG_STREAM << std::flush;
|
|
}
|