|
|
|
@@ -0,0 +1,831 @@
|
|
|
|
|
#include "mungal4.h"
|
|
|
|
|
#pragma hdrstop
|
|
|
|
|
|
|
|
|
|
//###########################################################################
|
|
|
|
|
// L4GLASSWIN -- per-display glass cockpit windows. Design + layout rules:
|
|
|
|
|
// l4glasswin.h. Each pod secondary display is its OWN GDI window carrying
|
|
|
|
|
// the display surface (CPU-blit from the shared gauge buffer) with its RIO
|
|
|
|
|
// button bank placed around it; a Flight Controls window hosts the
|
|
|
|
|
// no-display banks. Modeled on L4PADPANEL (GDI cells + lamp decode) and
|
|
|
|
|
// L4PLASMAWIN (CPU pixelBuffer -> StretchDIBits), so no D3D is involved.
|
|
|
|
|
//###########################################################################
|
|
|
|
|
|
|
|
|
|
#include "l4glasswin.h"
|
|
|
|
|
#include "l4padrio.h"
|
|
|
|
|
#include "l4vb16.h"
|
|
|
|
|
#include "../munga/gaugrend.h"
|
|
|
|
|
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
|
|
// The main game window (for pod-faithful placement) is reached via
|
|
|
|
|
// BTResolveMainWindow() (l4vb16.h) -- NOT the `ghWnd` global directly, which this
|
|
|
|
|
// newly-added engine TU can bind as the NULL /FORCE-duplicate copy.
|
|
|
|
|
|
|
|
|
|
//###########################################################################
|
|
|
|
|
// Mode gate
|
|
|
|
|
//###########################################################################
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
BTGlassPanelsActive()
|
|
|
|
|
{
|
|
|
|
|
static int v = -1;
|
|
|
|
|
if (v < 0)
|
|
|
|
|
{
|
|
|
|
|
const char *e = getenv("BT_GLASS_PANELS");
|
|
|
|
|
v = (e != NULL && e[0] != '0') ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//###########################################################################
|
|
|
|
|
// Layout constants (tunable -- Cyd flagged refinements to the exact button
|
|
|
|
|
// sub-positions + the yellow left/right split).
|
|
|
|
|
//###########################################################################
|
|
|
|
|
|
|
|
|
|
enum
|
|
|
|
|
{
|
|
|
|
|
Margin = 0, // no padding around the edges (surface fills the window)
|
|
|
|
|
TitleH = 0, // no in-client title band (the OS caption labels the window)
|
|
|
|
|
CellW = 58, // default button cell (flight-controls window)
|
|
|
|
|
CellH = 28,
|
|
|
|
|
RedCellH = 128, // red MFD buttons -- tall, extend ~100px into the display
|
|
|
|
|
EdgeProtrude = 10, // ...and this far OUT past the display edge
|
|
|
|
|
Gap = 4,
|
|
|
|
|
RailW = 40, // radar side-rail button thickness (rotated 90deg, fill height)
|
|
|
|
|
RailSlotAdd = 1, // rail pitch = 640/6 + 1 = 107 -> 104px buttons + 3px gaps = 639 (fits 640)
|
|
|
|
|
RailGap = 3, // vertical gap between rail buttons (own value; red/horizontals use Gap)
|
|
|
|
|
RedOffsetX = 5, // nudge the red MFD buttons right to line up with the imagery
|
|
|
|
|
MfdSurfW = 640, MfdSurfH = 480, // mono MFD surface at native 640x480
|
|
|
|
|
RadarSurfW = 480, RadarSurfH = 640, // portrait secondary/radar CRT at native (rotated 640x480)
|
|
|
|
|
RepaintTimerId = 1,
|
|
|
|
|
RepaintMilliseconds = 62, // == L4PADPANEL (half the fastest lamp half-period)
|
|
|
|
|
MfdMonoTint = 0x00FFFFFF // mono MFD phosphor colour (BGRA); tunable
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum ColorClass { ClrRed, ClrYellow, ClrBlue };
|
|
|
|
|
|
|
|
|
|
// Per-column horizontal nudge (px) for the 4 red MFD buttons, left->right, to line
|
|
|
|
|
// them up with the (unevenly-spaced) imagery legends. Tunable -- Cyd is dialing
|
|
|
|
|
// these in against DISPLAY / PROGRAM / etc. Applied on top of RedOffsetX.
|
|
|
|
|
static const int kRedColDX[4] = { -5, -4, 0, 0 };
|
|
|
|
|
|
|
|
|
|
struct GButton
|
|
|
|
|
{
|
|
|
|
|
int address;
|
|
|
|
|
ColorClass color;
|
|
|
|
|
RECT rect; // client-space
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct GWin
|
|
|
|
|
{
|
|
|
|
|
const char *title;
|
|
|
|
|
HWND hwnd;
|
|
|
|
|
|
|
|
|
|
// Surface (portPrimary == NULL -> no surface, e.g. Flight Controls).
|
|
|
|
|
const char *portPrimary;
|
|
|
|
|
const char *portAlt; // the Eng<n> preset plane sibling, or NULL
|
|
|
|
|
int monoTint; // -1 = palette-expand (radar); else BGRA mono tint
|
|
|
|
|
int rotate; // 0 / 1 / 3 (portrait secondary)
|
|
|
|
|
RECT surfaceRect;
|
|
|
|
|
|
|
|
|
|
GButton buttons[24];
|
|
|
|
|
int buttonCount;
|
|
|
|
|
int showLabels; // draw the hex/name on each button? (Flight Controls only)
|
|
|
|
|
|
|
|
|
|
int clientW, clientH;
|
|
|
|
|
int frameW, frameH;
|
|
|
|
|
int wantX, wantY;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static GWin gWins[8];
|
|
|
|
|
static int gWinCount = 0;
|
|
|
|
|
|
|
|
|
|
static int pressedAddress = -1;
|
|
|
|
|
static unsigned char latched[128];
|
|
|
|
|
static int gRadarRot = 3; // BT_GAUGE_SEC_ROT (default CW, upright)
|
|
|
|
|
static bool sRepositionedToMain = false;
|
|
|
|
|
static unsigned long *gStage = NULL; // 640*480 BGRA staging (shared, main-thread)
|
|
|
|
|
|
|
|
|
|
static HFONT titleFont = NULL;
|
|
|
|
|
static HFONT buttonFont = NULL;
|
|
|
|
|
static HFONT subFont = NULL;
|
|
|
|
|
|
|
|
|
|
//###########################################################################
|
|
|
|
|
// Named handle/joystick buttons (the Flight Controls window)
|
|
|
|
|
//###########################################################################
|
|
|
|
|
|
|
|
|
|
struct NamedButton { int address; const char *name; };
|
|
|
|
|
static const NamedButton namedButtons[] =
|
|
|
|
|
{
|
|
|
|
|
{ 0x3D, "Panic" }, { 0x3F, "Throttle" },
|
|
|
|
|
{ 0x40, "Main" }, { 0x41, "Hat Bk" }, { 0x42, "Hat Up" },
|
|
|
|
|
{ 0x43, "Hat R" }, { 0x44, "Hat L" }, { 0x45, "Pinky" },
|
|
|
|
|
{ 0x46, "Middle"}, { 0x47, "Upper" },
|
|
|
|
|
};
|
|
|
|
|
enum { NamedButtonCount = sizeof(namedButtons) / sizeof(namedButtons[0]) };
|
|
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
|
PhysicalName(int address)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < NamedButtonCount; ++i)
|
|
|
|
|
if (namedButtons[i].address == address)
|
|
|
|
|
return namedButtons[i].name;
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//###########################################################################
|
|
|
|
|
// Lamp decode (identical to L4PADPANEL::LampBrightnessOf -- RIOBase::LampState)
|
|
|
|
|
//###########################################################################
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
LampBrightnessOf(int state, unsigned long tick)
|
|
|
|
|
{
|
|
|
|
|
int level1 = (state >> 2) & 0x3;
|
|
|
|
|
int level2 = (state >> 4) & 0x3;
|
|
|
|
|
int level = (level1 > level2) ? level1 : level2;
|
|
|
|
|
if (level == 0)
|
|
|
|
|
return 0;
|
|
|
|
|
int flash = state & 0x3;
|
|
|
|
|
if (flash != 0)
|
|
|
|
|
{
|
|
|
|
|
unsigned long half_period = (flash == 1) ? 500 : (flash == 2) ? 250 : 125;
|
|
|
|
|
if ((tick / half_period) & 1)
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return level;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//###########################################################################
|
|
|
|
|
// Layout builders
|
|
|
|
|
//###########################################################################
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
PlaceCellAt(GWin &w, int address, int x, int y, ColorClass color)
|
|
|
|
|
{
|
|
|
|
|
GButton &b = w.buttons[w.buttonCount++];
|
|
|
|
|
b.address = address;
|
|
|
|
|
b.color = color;
|
|
|
|
|
b.rect.left = x + 2;
|
|
|
|
|
b.rect.top = y + 2;
|
|
|
|
|
b.rect.right = x + CellW - 2;
|
|
|
|
|
b.rect.bottom = y + CellH - 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
PlaceLine(GWin &w, int startAddr, int count, int step,
|
|
|
|
|
int x, int y, int horizontal, ColorClass color)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < count; ++i)
|
|
|
|
|
{
|
|
|
|
|
int cx = horizontal ? (x + i * CellW) : x;
|
|
|
|
|
int cy = horizontal ? y : (y + i * CellH);
|
|
|
|
|
PlaceCellAt(w, startAddr + i * step, cx, cy, color);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Place a button with an explicit size (custom-shaped cells: wide red buttons,
|
|
|
|
|
// tall rotated radar rails).
|
|
|
|
|
static void
|
|
|
|
|
PlaceRect(GWin &w, int address, int x, int y, int cw, int ch, ColorClass color)
|
|
|
|
|
{
|
|
|
|
|
GButton &b = w.buttons[w.buttonCount++];
|
|
|
|
|
b.address = address;
|
|
|
|
|
b.color = color;
|
|
|
|
|
b.rect.left = x;
|
|
|
|
|
b.rect.top = y;
|
|
|
|
|
b.rect.right = x + cw;
|
|
|
|
|
b.rect.bottom = y + ch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// An MFD display: 8 red buttons split 4 above / 4 below the surface (top row =
|
|
|
|
|
// the high 4 addresses descending, matching the L4PADPANEL cluster order).
|
|
|
|
|
static void
|
|
|
|
|
BuildMfd(GWin &w, const char *title, const char *portP, const char *portA, int bankHi)
|
|
|
|
|
{
|
|
|
|
|
memset(&w, 0, sizeof(w));
|
|
|
|
|
w.title = title;
|
|
|
|
|
w.portPrimary = portP;
|
|
|
|
|
w.portAlt = portA;
|
|
|
|
|
w.monoTint = MfdMonoTint;
|
|
|
|
|
w.rotate = 0;
|
|
|
|
|
|
|
|
|
|
int surfX = Margin;
|
|
|
|
|
int surfY = Margin + TitleH + EdgeProtrude; // room for the top buttons to poke out
|
|
|
|
|
|
|
|
|
|
// Buttons OVERLAY the display: 4 tile the full width, reaching RedCellH INTO the
|
|
|
|
|
// display and EdgeProtrude OUT past the edge (top row + bottom row; hi addresses
|
|
|
|
|
// descending -- L4PADPANEL order).
|
|
|
|
|
int slotW = MfdSurfW / 4;
|
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
|
|
|
{
|
|
|
|
|
int x = surfX + i * slotW + RedOffsetX + kRedColDX[i];
|
|
|
|
|
PlaceRect(w, bankHi - i, x, surfY - EdgeProtrude, slotW - Gap, RedCellH + EdgeProtrude, ClrRed); // top
|
|
|
|
|
PlaceRect(w, bankHi - 4 - i, x, surfY + MfdSurfH - RedCellH, slotW - Gap, RedCellH + EdgeProtrude, ClrRed); // bottom
|
|
|
|
|
}
|
|
|
|
|
SetRect(&w.surfaceRect, surfX, surfY, surfX + MfdSurfW, surfY + MfdSurfH);
|
|
|
|
|
|
|
|
|
|
w.clientW = Margin + MfdSurfW + Margin;
|
|
|
|
|
w.clientH = surfY + MfdSurfH + EdgeProtrude + Margin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The secondary/radar display: 12 yellow Secondary buttons split left / right
|
|
|
|
|
// of the portrait surface (0x10-0x15 left, 0x16-0x1B right).
|
|
|
|
|
static void
|
|
|
|
|
BuildRadar(GWin &w)
|
|
|
|
|
{
|
|
|
|
|
memset(&w, 0, sizeof(w));
|
|
|
|
|
w.title = "Secondary / Radar";
|
|
|
|
|
w.portPrimary = "sec";
|
|
|
|
|
w.portAlt = NULL;
|
|
|
|
|
w.monoTint = -1; // palette
|
|
|
|
|
w.rotate = gRadarRot;
|
|
|
|
|
|
|
|
|
|
int surfX = Margin + EdgeProtrude; // room for the left rail to poke out
|
|
|
|
|
int surfY = Margin + TitleH;
|
|
|
|
|
|
|
|
|
|
// Rails: big clickable strips that reach RailW INTO the display (hidden under the
|
|
|
|
|
// imagery) and poke EdgeProtrude OUT past the side edge (the lamp light).
|
|
|
|
|
// Left 0x10..0x15, right 0x18 (top)..0x1D (bottom). (0x15 kept on the left rail
|
|
|
|
|
// -- Cyd listed 0x10-0x14; move it if wanted.)
|
|
|
|
|
int railCount = 6;
|
|
|
|
|
int slotH = RadarSurfH / railCount + RailSlotAdd;
|
|
|
|
|
int leftX = surfX - EdgeProtrude;
|
|
|
|
|
int rightX = surfX + RadarSurfW - RailW;
|
|
|
|
|
for (int i = 0; i < railCount; ++i)
|
|
|
|
|
{
|
|
|
|
|
int y = surfY + i * slotH;
|
|
|
|
|
PlaceRect(w, 0x10 + i, leftX, y, EdgeProtrude + RailW, slotH - RailGap, ClrYellow);
|
|
|
|
|
PlaceRect(w, 0x18 + i, rightX, y, RailW + EdgeProtrude, slotH - RailGap, ClrYellow);
|
|
|
|
|
}
|
|
|
|
|
SetRect(&w.surfaceRect, surfX, surfY, surfX + RadarSurfW, surfY + RadarSurfH);
|
|
|
|
|
|
|
|
|
|
// Bottom row: 16, 17, 1F, 1E (left to right), BETWEEN the rails -- reaches CellH
|
|
|
|
|
// up into the display, pokes EdgeProtrude out the bottom.
|
|
|
|
|
static const int bottomAddrs[4] = { 0x16, 0x17, 0x1F, 0x1E };
|
|
|
|
|
int botLeft = surfX + RailW;
|
|
|
|
|
int botSpan = RadarSurfW - 2 * RailW;
|
|
|
|
|
int botW = botSpan / 4;
|
|
|
|
|
int botY = surfY + RadarSurfH - CellH;
|
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
|
|
|
PlaceRect(w, bottomAddrs[i], botLeft + i * botW, botY, botW - Gap, CellH + EdgeProtrude, ClrYellow);
|
|
|
|
|
|
|
|
|
|
w.clientW = surfX + RadarSurfW + EdgeProtrude + Margin;
|
|
|
|
|
w.clientH = surfY + RadarSurfH + EdgeProtrude + Margin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The Flight Controls window: the no-display banks -- throttle/panic/door/icom
|
|
|
|
|
// (0x38-0x3F) and joystick/fire (0x40-0x47), two neutral columns.
|
|
|
|
|
static void
|
|
|
|
|
BuildFlight(GWin &w)
|
|
|
|
|
{
|
|
|
|
|
memset(&w, 0, sizeof(w));
|
|
|
|
|
w.title = "Flight Controls";
|
|
|
|
|
w.portPrimary = NULL;
|
|
|
|
|
w.showLabels = 1; // the only window that shows hex/name on its buttons
|
|
|
|
|
|
|
|
|
|
int topY = Margin + TitleH;
|
|
|
|
|
int col1 = Margin;
|
|
|
|
|
int col2 = Margin + CellW + Gap;
|
|
|
|
|
|
|
|
|
|
PlaceLine(w, 0x38, 8, +1, col1, topY, 0, ClrBlue); // throttle / panic / door / icom
|
|
|
|
|
PlaceLine(w, 0x40, 8, +1, col2, topY, 0, ClrBlue); // joystick + fire buttons
|
|
|
|
|
|
|
|
|
|
w.clientW = col2 + CellW + Margin;
|
|
|
|
|
w.clientH = topY + 8 * CellH + Margin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//###########################################################################
|
|
|
|
|
// Placement -- pod-faithful ring around the main game window
|
|
|
|
|
//###########################################################################
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
WorkArea(RECT *wa)
|
|
|
|
|
{
|
|
|
|
|
if (!SystemParametersInfoW(SPI_GETWORKAREA, 0, wa, 0))
|
|
|
|
|
{
|
|
|
|
|
wa->left = 0; wa->top = 0;
|
|
|
|
|
wa->right = GetSystemMetrics(SM_CXSCREEN);
|
|
|
|
|
wa->bottom = GetSystemMetrics(SM_CYSCREEN);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
GetMainRect(RECT *out)
|
|
|
|
|
{
|
|
|
|
|
HWND mh = (HWND)BTResolveMainWindow();
|
|
|
|
|
if (mh != NULL && IsWindow(mh) && GetWindowRect(mh, out))
|
|
|
|
|
return true;
|
|
|
|
|
// Fallback before the main window exists: assume 800x600 centered.
|
|
|
|
|
RECT wa; WorkArea(&wa);
|
|
|
|
|
int cw = 800, ch = 600;
|
|
|
|
|
out->left = (wa.left + wa.right - cw) / 2;
|
|
|
|
|
out->top = (wa.top + wa.bottom - ch) / 2;
|
|
|
|
|
out->right = out->left + cw;
|
|
|
|
|
out->bottom = out->top + ch;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
ComputeLayout()
|
|
|
|
|
{
|
|
|
|
|
RECT m; GetMainRect(&m);
|
|
|
|
|
RECT wa; WorkArea(&wa);
|
|
|
|
|
int gap = 10;
|
|
|
|
|
int cx = (m.left + m.right) / 2;
|
|
|
|
|
int cy = (m.top + m.bottom) / 2;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < gWinCount; ++i)
|
|
|
|
|
{
|
|
|
|
|
GWin &w = gWins[i];
|
|
|
|
|
int fw = w.frameW, fh = w.frameH;
|
|
|
|
|
int x = m.left, y = m.top;
|
|
|
|
|
switch (i)
|
|
|
|
|
{
|
|
|
|
|
case 0: x = m.left; y = m.top - fh - gap; break; // Heat (UL)
|
|
|
|
|
case 1: x = cx - fw / 2; y = m.top - fh - gap; break; // Mfd2 (UC)
|
|
|
|
|
case 2: x = m.right - fw; y = m.top - fh - gap; break; // Comm (UR)
|
|
|
|
|
case 3: x = m.left - fw - gap; y = m.bottom - fh; break; // Mfd1 (LL)
|
|
|
|
|
case 4: x = m.right + gap; y = m.bottom - fh; break; // Mfd3 (LR)
|
|
|
|
|
case 5: x = cx - fw / 2; y = m.bottom + gap; break; // Radar (center)
|
|
|
|
|
case 6: x = m.left - fw - gap; y = cy - fh / 2; break; // Flight (left of seat)
|
|
|
|
|
}
|
|
|
|
|
// Clamp fully on-screen (small desktops -> panels pile at the edges).
|
|
|
|
|
if (x + fw > wa.right) x = wa.right - fw;
|
|
|
|
|
if (y + fh > wa.bottom) y = wa.bottom - fh;
|
|
|
|
|
if (x < wa.left) x = wa.left;
|
|
|
|
|
if (y < wa.top) y = wa.top;
|
|
|
|
|
w.wantX = x;
|
|
|
|
|
w.wantY = y;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
ApplyLayout()
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < gWinCount; ++i)
|
|
|
|
|
if (gWins[i].hwnd != NULL)
|
|
|
|
|
SetWindowPos(gWins[i].hwnd, HWND_TOPMOST, gWins[i].wantX, gWins[i].wantY,
|
|
|
|
|
0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//###########################################################################
|
|
|
|
|
// Painting
|
|
|
|
|
//###########################################################################
|
|
|
|
|
|
|
|
|
|
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 int sGlassLog = -1;
|
|
|
|
|
static unsigned long sBlitCalls = 0;
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
BlitSurface(HDC dc, GWin *w)
|
|
|
|
|
{
|
|
|
|
|
if (sGlassLog < 0)
|
|
|
|
|
sGlassLog = getenv("BT_GLASS_LOG") ? 1 : 0;
|
|
|
|
|
int log = sGlassLog && ((sBlitCalls++ % 240) == 0);
|
|
|
|
|
|
|
|
|
|
// Reach the renderer via BTResolveGaugeRenderer (l4vb16.h; DEFINED in
|
|
|
|
|
// game/btl4main.cpp off the real btl4App pointer), NOT the `application` global
|
|
|
|
|
// directly -- a fresh engine TU can bind the NULL /FORCE-duplicate copy.
|
|
|
|
|
GaugeRenderer *gr = BTResolveGaugeRenderer();
|
|
|
|
|
if (gr == NULL)
|
|
|
|
|
{
|
|
|
|
|
if (log) DEBUG_STREAM << "[glass] '" << w->title << "' gauge renderer not ready\n" << std::flush;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
L4GraphicsPort *port = static_cast<L4GraphicsPort*>(gr->GetGraphicsPort(w->portPrimary));
|
|
|
|
|
if (w->portAlt != NULL)
|
|
|
|
|
{
|
|
|
|
|
L4GraphicsPort *alt = static_cast<L4GraphicsPort*>(gr->GetGraphicsPort(w->portAlt));
|
|
|
|
|
// Show whichever of the Mfd<n>/Eng<n> pair is the live (non-blank) plane,
|
|
|
|
|
// exactly like the pod monitor + BTDrawGaugeSurfaces (Gitea #9).
|
|
|
|
|
bool primaryBlank = (port == NULL) ||
|
|
|
|
|
(port->GetEnableID() == L4GraphicsPort::BlankColor);
|
|
|
|
|
if (primaryBlank && alt != NULL &&
|
|
|
|
|
alt->GetEnableID() != L4GraphicsPort::BlankColor)
|
|
|
|
|
port = alt;
|
|
|
|
|
else if (port == NULL)
|
|
|
|
|
port = alt;
|
|
|
|
|
}
|
|
|
|
|
if (port == NULL)
|
|
|
|
|
{
|
|
|
|
|
if (log) DEBUG_STREAM << "[glass] '" << w->title << "' port '" << w->portPrimary
|
|
|
|
|
<< "' NULL (not installed)\n" << std::flush;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SVGA16 *svga = static_cast<SVGA16*>(port->graphicsDisplay);
|
|
|
|
|
if (svga == NULL || gStage == NULL)
|
|
|
|
|
{
|
|
|
|
|
if (log) DEBUG_STREAM << "[glass] '" << w->title << "' svga=" << (void*)svga
|
|
|
|
|
<< " stage=" << (void*)gStage << "\n" << std::flush;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Surface expand. BT_GLASS_MFD_PAL (experiment): render the mono MFD windows
|
|
|
|
|
// through the palette LUT (low byte, secondary palette) instead of the 1-bit
|
|
|
|
|
// mono tint, to compare. NB the MFD's own graphics live in a HIGH bit-plane
|
|
|
|
|
// the 256-entry palette can't index, so this shows the shared low-byte content.
|
|
|
|
|
int mask = port->GetBitMask();
|
|
|
|
|
int palId = port->paletteID;
|
|
|
|
|
int tint = w->monoTint;
|
|
|
|
|
static int sMfdPal = -1;
|
|
|
|
|
if (sMfdPal < 0) sMfdPal = getenv("BT_GLASS_MFD_PAL") ? 1 : 0;
|
|
|
|
|
if (sMfdPal && tint >= 0)
|
|
|
|
|
{
|
|
|
|
|
tint = -1; // palette-expand
|
|
|
|
|
mask = 0xFF; // index the low (palette) byte
|
|
|
|
|
palId = SVGA16::SecondaryPalette;
|
|
|
|
|
}
|
|
|
|
|
int ow = 0, oh = 0;
|
|
|
|
|
svga->ExpandPlaneToBGRA(mask, palId, tint, w->rotate, gStage, &ow, &oh);
|
|
|
|
|
if (log)
|
|
|
|
|
{
|
|
|
|
|
int nz = 0, n = ow * oh;
|
|
|
|
|
for (int i = 0; i < n; ++i) if (gStage[i] != 0) nz++;
|
|
|
|
|
DEBUG_STREAM << "[glass] '" << w->title << "' port=" << w->portPrimary
|
|
|
|
|
<< " mask=0x" << std::hex << port->GetBitMask() << std::dec
|
|
|
|
|
<< " pal=" << port->paletteID << " enable=" << port->GetEnableID()
|
|
|
|
|
<< " ow=" << ow << " oh=" << oh << " nonzero=" << nz << "/" << n
|
|
|
|
|
<< "\n" << std::flush;
|
|
|
|
|
}
|
|
|
|
|
if (ow <= 0 || oh <= 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
BITMAPINFO info;
|
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
|
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
|
|
|
info.bmiHeader.biWidth = ow;
|
|
|
|
|
info.bmiHeader.biHeight = -oh; // top-down
|
|
|
|
|
info.bmiHeader.biPlanes = 1;
|
|
|
|
|
info.bmiHeader.biBitCount = 32;
|
|
|
|
|
info.bmiHeader.biCompression = BI_RGB;
|
|
|
|
|
|
|
|
|
|
const RECT &r = w->surfaceRect;
|
|
|
|
|
SetStretchBltMode(dc, HALFTONE);
|
|
|
|
|
SetBrushOrgEx(dc, 0, 0, NULL);
|
|
|
|
|
StretchDIBits(dc,
|
|
|
|
|
r.left, r.top, r.right - r.left, r.bottom - r.top,
|
|
|
|
|
0, 0, ow, oh,
|
|
|
|
|
gStage, &info, DIB_RGB_COLORS, SRCCOPY);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
PaintGlass(HWND window, GWin *w)
|
|
|
|
|
{
|
|
|
|
|
PAINTSTRUCT paint;
|
|
|
|
|
HDC winDC = BeginPaint(window, &paint);
|
|
|
|
|
unsigned long tick = GetTickCount();
|
|
|
|
|
|
|
|
|
|
RECT client;
|
|
|
|
|
GetClientRect(window, &client);
|
|
|
|
|
|
|
|
|
|
// Double-buffer the whole frame (== L4PADPANEL issue #13).
|
|
|
|
|
HDC dc = CreateCompatibleDC(winDC);
|
|
|
|
|
HBITMAP backing = CreateCompatibleBitmap(winDC,
|
|
|
|
|
client.right - client.left, client.bottom - client.top);
|
|
|
|
|
HBITMAP oldBacking = (HBITMAP)SelectObject(dc, backing);
|
|
|
|
|
HBRUSH background = CreateSolidBrush(RGB(24, 24, 24));
|
|
|
|
|
FillRect(dc, &client, background);
|
|
|
|
|
DeleteObject(background);
|
|
|
|
|
SetBkMode(dc, TRANSPARENT);
|
|
|
|
|
|
|
|
|
|
// (No in-client title -- the OS window caption labels each window.)
|
|
|
|
|
|
|
|
|
|
// Buttons are drawn FIRST (UNDER the display). Each is a big clickable rect;
|
|
|
|
|
// the surface blit further down masks the part inside the display, leaving only
|
|
|
|
|
// the bit that protrudes past the display edge visible -- a solid lamp light.
|
|
|
|
|
// (The Flight Controls window has no surface, so its buttons show in full.)
|
|
|
|
|
for (int i = 0; i < w->buttonCount; ++i)
|
|
|
|
|
{
|
|
|
|
|
const GButton &b = w->buttons[i];
|
|
|
|
|
const RECT &r = b.rect;
|
|
|
|
|
int held = (b.address == pressedAddress) || latched[b.address & 0x7F];
|
|
|
|
|
int shade = LampBrightnessOf(PadRIO::GetLampState(b.address), tick);
|
|
|
|
|
if (held)
|
|
|
|
|
shade = 3;
|
|
|
|
|
|
|
|
|
|
COLORREF fill, text_color;
|
|
|
|
|
if (b.color == ClrYellow)
|
|
|
|
|
{
|
|
|
|
|
fill = (shade == 3) ? RGB(245, 210, 60)
|
|
|
|
|
: (shade != 0) ? RGB(140, 118, 38)
|
|
|
|
|
: RGB(70, 60, 24);
|
|
|
|
|
text_color = RGB(0, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
else if (b.color == ClrBlue)
|
|
|
|
|
{
|
|
|
|
|
fill = (shade == 3) ? RGB(205, 228, 255)
|
|
|
|
|
: (shade != 0) ? RGB(110, 135, 180)
|
|
|
|
|
: RGB(70, 86, 120);
|
|
|
|
|
text_color = RGB(0, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
else // ClrRed (MFD)
|
|
|
|
|
{
|
|
|
|
|
fill = (shade == 3) ? RGB(230, 70, 70)
|
|
|
|
|
: (shade != 0) ? RGB(120, 50, 50)
|
|
|
|
|
: RGB(64, 40, 40);
|
|
|
|
|
text_color = RGB(255, 255, 255);
|
|
|
|
|
}
|
|
|
|
|
// Solid lamp fill + a bright border. Under the imagery, only the protruding
|
|
|
|
|
// edge shows once the surface is blitted on top.
|
|
|
|
|
{
|
|
|
|
|
COLORREF bright = (b.color == ClrYellow) ? RGB(245, 210, 60)
|
|
|
|
|
: (b.color == ClrBlue) ? RGB(205, 228, 255)
|
|
|
|
|
: RGB(230, 70, 70);
|
|
|
|
|
HBRUSH face = CreateSolidBrush(fill);
|
|
|
|
|
FillRect(dc, &r, face);
|
|
|
|
|
DeleteObject(face);
|
|
|
|
|
HPEN pen = CreatePen(PS_SOLID, 1, bright);
|
|
|
|
|
HGDIOBJ oldPen = SelectObject(dc, pen);
|
|
|
|
|
HGDIOBJ oldBr = SelectObject(dc, GetStockObject(NULL_BRUSH));
|
|
|
|
|
Rectangle(dc, r.left, r.top, r.right, r.bottom);
|
|
|
|
|
SelectObject(dc, oldBr);
|
|
|
|
|
SelectObject(dc, oldPen);
|
|
|
|
|
DeleteObject(pen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Labels only on the Flight Controls window; MFD/radar buttons are bare.
|
|
|
|
|
if (w->showLabels)
|
|
|
|
|
{
|
|
|
|
|
char hex[8];
|
|
|
|
|
sprintf(hex, "%02X", b.address);
|
|
|
|
|
const char *name = PhysicalName(b.address);
|
|
|
|
|
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; momentary held = white outline (== L4PADPANEL).
|
|
|
|
|
if (latched[b.address & 0x7F])
|
|
|
|
|
{
|
|
|
|
|
HBRUSH gold = CreateSolidBrush(RGB(255, 215, 0));
|
|
|
|
|
RECT o = r;
|
|
|
|
|
FrameRect(dc, &o, gold);
|
|
|
|
|
DeleteObject(gold);
|
|
|
|
|
}
|
|
|
|
|
else if (held)
|
|
|
|
|
{
|
|
|
|
|
RECT o = r;
|
|
|
|
|
FrameRect(dc, &o, (HBRUSH)GetStockObject(WHITE_BRUSH));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Surface (imagery) LAST -- ON TOP of the buttons, masking their inside so only
|
|
|
|
|
// the protruding lamp edges show. (No-op for the Flight Controls window.)
|
|
|
|
|
if (w->portPrimary != NULL)
|
|
|
|
|
{
|
|
|
|
|
BlitSurface(dc, w);
|
|
|
|
|
HPEN pen = CreatePen(PS_SOLID, 1, RGB(70, 70, 70));
|
|
|
|
|
HPEN oldPen = (HPEN)SelectObject(dc, pen);
|
|
|
|
|
HBRUSH oldBrush = (HBRUSH)SelectObject(dc, GetStockObject(NULL_BRUSH));
|
|
|
|
|
Rectangle(dc, w->surfaceRect.left - 1, w->surfaceRect.top - 1,
|
|
|
|
|
w->surfaceRect.right + 1, w->surfaceRect.bottom + 1);
|
|
|
|
|
SelectObject(dc, oldBrush);
|
|
|
|
|
SelectObject(dc, oldPen);
|
|
|
|
|
DeleteObject(pen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BitBlt(winDC, 0, 0, client.right - client.left, client.bottom - client.top,
|
|
|
|
|
dc, 0, 0, SRCCOPY);
|
|
|
|
|
SelectObject(dc, oldBacking);
|
|
|
|
|
DeleteObject(backing);
|
|
|
|
|
DeleteDC(dc);
|
|
|
|
|
EndPaint(window, &paint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//###########################################################################
|
|
|
|
|
// WndProc / input
|
|
|
|
|
//###########################################################################
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
HitTest(GWin *w, int x, int y)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < w->buttonCount; ++i)
|
|
|
|
|
{
|
|
|
|
|
const RECT &r = w->buttons[i].rect;
|
|
|
|
|
if (x >= r.left && x < r.right && y >= r.top && y < r.bottom)
|
|
|
|
|
return w->buttons[i].address;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static LRESULT CALLBACK
|
|
|
|
|
GlassWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
|
|
|
|
|
{
|
|
|
|
|
GWin *w = (GWin*)GetWindowLongPtrW(window, GWLP_USERDATA);
|
|
|
|
|
|
|
|
|
|
switch (message)
|
|
|
|
|
{
|
|
|
|
|
case WM_PAINT:
|
|
|
|
|
if (w != NULL) { PaintGlass(window, w); return 0; }
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case WM_ERASEBKGND:
|
|
|
|
|
return 1; // PaintGlass fills the frame (double-buffered)
|
|
|
|
|
|
|
|
|
|
case WM_TIMER:
|
|
|
|
|
if (wparam == RepaintTimerId)
|
|
|
|
|
{
|
|
|
|
|
// One-shot: once the real main window is up, snap the ring onto it.
|
|
|
|
|
HWND mh = (HWND)BTResolveMainWindow();
|
|
|
|
|
if (!sRepositionedToMain && mh != NULL && IsWindow(mh))
|
|
|
|
|
{
|
|
|
|
|
ComputeLayout();
|
|
|
|
|
ApplyLayout();
|
|
|
|
|
sRepositionedToMain = true;
|
|
|
|
|
}
|
|
|
|
|
InvalidateRect(window, NULL, FALSE);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
case WM_LBUTTONDOWN:
|
|
|
|
|
if (w != NULL)
|
|
|
|
|
{
|
|
|
|
|
int a = HitTest(w, (int)(short)LOWORD(lparam), (int)(short)HIWORD(lparam));
|
|
|
|
|
if (a >= 0)
|
|
|
|
|
{
|
|
|
|
|
if (latched[a & 0x7F])
|
|
|
|
|
{
|
|
|
|
|
latched[a & 0x7F] = 0;
|
|
|
|
|
PadRIO::SetScreenButton(a, 0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pressedAddress = a;
|
|
|
|
|
SetCapture(window);
|
|
|
|
|
PadRIO::SetScreenButton(a, 1);
|
|
|
|
|
}
|
|
|
|
|
InvalidateRect(window, NULL, FALSE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
case WM_LBUTTONUP:
|
|
|
|
|
if (pressedAddress >= 0)
|
|
|
|
|
{
|
|
|
|
|
PadRIO::SetScreenButton(pressedAddress, 0);
|
|
|
|
|
pressedAddress = -1;
|
|
|
|
|
ReleaseCapture();
|
|
|
|
|
InvalidateRect(window, NULL, FALSE);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
case WM_RBUTTONDOWN:
|
|
|
|
|
if (w != NULL)
|
|
|
|
|
{
|
|
|
|
|
int a = HitTest(w, (int)(short)LOWORD(lparam), (int)(short)HIWORD(lparam));
|
|
|
|
|
if (a >= 0)
|
|
|
|
|
{
|
|
|
|
|
latched[a & 0x7F] = latched[a & 0x7F] ? 0 : 1;
|
|
|
|
|
PadRIO::SetScreenButton(a, latched[a & 0x7F]);
|
|
|
|
|
InvalidateRect(window, NULL, FALSE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
case WM_CLOSE:
|
|
|
|
|
ShowWindow(window, SW_HIDE); // hide; the device owns the lifetime
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return DefWindowProcW(window, message, wparam, lparam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//###########################################################################
|
|
|
|
|
// Create / destroy
|
|
|
|
|
//###########################################################################
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
BTGlassPanels_Create()
|
|
|
|
|
{
|
|
|
|
|
if (gWinCount != 0)
|
|
|
|
|
return; // already up
|
|
|
|
|
|
|
|
|
|
// BT_GAUGE_SEC_ROT: the portrait-CRT unrotation direction (shared with the
|
|
|
|
|
// D3D dev path; default 3 = CW = user-verified upright).
|
|
|
|
|
{
|
|
|
|
|
const char *rv = getenv("BT_GAUGE_SEC_ROT");
|
|
|
|
|
gRadarRot = (rv != NULL && rv[0] == '1') ? 1 : 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memset(latched, 0, sizeof(latched));
|
|
|
|
|
pressedAddress = -1;
|
|
|
|
|
sRepositionedToMain = false;
|
|
|
|
|
if (gStage == NULL)
|
|
|
|
|
gStage = new unsigned long[640 * 480];
|
|
|
|
|
|
|
|
|
|
// Build the seven windows. Order fixes the ComputeLayout roles (0..6).
|
|
|
|
|
BuildMfd (gWins[0], "Heat MFD", "Heat", NULL, 0x2F); // UL 0x28-0x2F
|
|
|
|
|
BuildMfd (gWins[1], "Engineering", "Mfd2", "Eng2", 0x27); // UC 0x20-0x27
|
|
|
|
|
BuildMfd (gWins[2], "Comm MFD", "Comm", NULL, 0x37); // UR 0x30-0x37
|
|
|
|
|
BuildMfd (gWins[3], "Left Weapons", "Mfd1", "Eng1", 0x0F); // LL 0x08-0x0F
|
|
|
|
|
BuildMfd (gWins[4], "Right Weapons", "Mfd3", "Eng3", 0x07); // LR 0x00-0x07
|
|
|
|
|
BuildRadar(gWins[5]); // center 0x10-0x1B
|
|
|
|
|
BuildFlight(gWins[6]); // 0x38-0x47
|
|
|
|
|
gWinCount = 7;
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
WNDCLASSW wc;
|
|
|
|
|
memset(&wc, 0, sizeof(wc));
|
|
|
|
|
wc.lpfnWndProc = GlassWndProc;
|
|
|
|
|
wc.hInstance = GetModuleHandleW(NULL);
|
|
|
|
|
wc.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
|
|
|
|
|
wc.lpszClassName = L"BTGlassWin";
|
|
|
|
|
RegisterClassW(&wc);
|
|
|
|
|
|
|
|
|
|
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
|
|
|
|
|
|
|
|
|
|
// Frame sizes first (ComputeLayout positions by frame dims).
|
|
|
|
|
for (int i = 0; i < gWinCount; ++i)
|
|
|
|
|
{
|
|
|
|
|
RECT fr = { 0, 0, gWins[i].clientW, gWins[i].clientH };
|
|
|
|
|
AdjustWindowRect(&fr, style, FALSE);
|
|
|
|
|
gWins[i].frameW = fr.right - fr.left;
|
|
|
|
|
gWins[i].frameH = fr.bottom - fr.top;
|
|
|
|
|
}
|
|
|
|
|
ComputeLayout();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < gWinCount; ++i)
|
|
|
|
|
{
|
|
|
|
|
GWin &w = gWins[i];
|
|
|
|
|
WCHAR wtitle[64];
|
|
|
|
|
int n = 0;
|
|
|
|
|
for (const char *s = w.title; *s && n < 63; ++s) wtitle[n++] = (WCHAR)*s;
|
|
|
|
|
wtitle[n] = 0;
|
|
|
|
|
|
|
|
|
|
// Layered + topmost (== L4PADPANEL): DWM composites the panel independently
|
|
|
|
|
// of the game's D3D swap chain, ending GDI-vs-D3D presentation strobing.
|
|
|
|
|
w.hwnd = CreateWindowExW(
|
|
|
|
|
WS_EX_TOPMOST | WS_EX_LAYERED,
|
|
|
|
|
L"BTGlassWin", wtitle, style,
|
|
|
|
|
w.wantX, w.wantY, w.frameW, w.frameH,
|
|
|
|
|
NULL, NULL, GetModuleHandleW(NULL), NULL);
|
|
|
|
|
if (w.hwnd == NULL)
|
|
|
|
|
{
|
|
|
|
|
DEBUG_STREAM << "[glasswin] CreateWindow failed for '" << w.title
|
|
|
|
|
<< "'\n" << std::flush;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
SetWindowLongPtrW(w.hwnd, GWLP_USERDATA, (LONG_PTR)&w);
|
|
|
|
|
SetLayeredWindowAttributes(w.hwnd, 0, 255, LWA_ALPHA);
|
|
|
|
|
SetTimer(w.hwnd, RepaintTimerId, RepaintMilliseconds, NULL);
|
|
|
|
|
ShowWindow(w.hwnd, SW_SHOWNOACTIVATE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DEBUG_STREAM << "[glasswin] per-display cockpit up (" << gWinCount
|
|
|
|
|
<< " windows: 5 MFD + radar + flight)\n" << std::flush;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
BTGlassPanels_Destroy()
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < gWinCount; ++i)
|
|
|
|
|
{
|
|
|
|
|
if (gWins[i].hwnd != NULL)
|
|
|
|
|
{
|
|
|
|
|
KillTimer(gWins[i].hwnd, RepaintTimerId);
|
|
|
|
|
DestroyWindow(gWins[i].hwnd);
|
|
|
|
|
gWins[i].hwnd = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
gWinCount = 0;
|
|
|
|
|
if (titleFont) { DeleteObject(titleFont); titleFont = NULL; }
|
|
|
|
|
if (buttonFont) { DeleteObject(buttonFont); buttonFont = NULL; }
|
|
|
|
|
if (subFont) { DeleteObject(subFont); subFont = NULL; }
|
|
|
|
|
if (gStage) { delete[] gStage; gStage = NULL; }
|
|
|
|
|
}
|