#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 #include 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); Push(out, baseAddress + i, x, y, cw, ch, 2, (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; }