Glass cockpit: per-display windows (BT_GLASS_PANELS)
Break the desktop glass cockpit's secondary displays out of the single combined pad panel + D3D gauge strip into ONE window per pod display, each carrying that display's surface with its RIO button bank, arranged pod-faithfully around the main view. New TU engine/MUNGA_L4/L4GLASSWIN.* (BT_GLASS-only), selected by the -platform glass preset via the new runtime gate BT_GLASS_PANELS (=0 falls back to the legacy single panel + dock). - Surfaces are CPU-expanded from the shared gauge buffer (SVGA16::ExpandPlaneToBGRA, no D3D) and StretchDIBits'd in -- the D3D dev-composite path (dock / window / overlay) stands down while active. - Buttons sit UNDER the imagery: big hidden click targets that reach into the display, with only a small protruding edge showing as a lamp light. Red MFD banks tile the top/bottom; radar rails run the sides + a bottom row; a Flight Controls window hosts the no-display banks. - Windows: Heat / Engineering / Comm / Left Weapons / Right Weapons / radar + Flight Controls; edge-to-edge (no in-client margin/title; OS caption labels each). - Fix blank surfaces: application/ghWnd are duplicate-defined and bind non-deterministically under /FORCE, so the fresh L4GLASSWIN TU read NULL. Reach the renderer/window via BTResolveGaugeRenderer/BTResolveMainWindow defined in btl4main.cpp off the real btl4App/hWnd pointers. New gotcha: reconstruction-gotchas.md S6 duplicate-GLOBAL corollary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -253,6 +253,7 @@ if(BT_GLASS)
|
||||
"engine/MUNGA_L4/L4PADRIO.cpp"
|
||||
"engine/MUNGA_L4/L4PADBINDINGS.cpp"
|
||||
"engine/MUNGA_L4/L4PADPANEL.cpp"
|
||||
"engine/MUNGA_L4/L4GLASSWIN.cpp"
|
||||
"engine/MUNGA_L4/L4PLASMAWIN.cpp"
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -18,7 +18,7 @@ every trade-off.
|
||||
|
||||
| Gate | Default | Adds |
|
||||
|---|---|---|
|
||||
| `BT_GLASS` | OFF | PadRIO input (XInput+keyboard), on-screen cockpit button panel, desktop plasma window, `glass` platform preset, miniconsole mission launcher |
|
||||
| `BT_GLASS` | OFF | PadRIO input (XInput+keyboard), per-display cockpit windows (or the legacy single button panel), desktop plasma window, `glass` platform preset, miniconsole mission launcher |
|
||||
| `BT_STEAM` | OFF (requires `BT_GLASS`) | Steam transport (`ISteamNetworkingSockets`) + `ISteamMatchmaking` lobby |
|
||||
|
||||
- A **no-flags configure is the pure pod game** — the build of record. `cmake` options are
|
||||
@@ -85,6 +85,34 @@ copied from them.
|
||||
unbound keys keep their authentic meanings (5/z presets, t-o pilot select, +/- zoom).
|
||||
In BAS control mode the pod turns with the STICK (Q/E) and pedals are inactive — authentic;
|
||||
A/D turn in MID/ADV. Full audit + census: `docs/GLASS_COCKPIT.md` §2026-07-20.
|
||||
- **Per-display cockpit windows (`BT_GLASS_PANELS`): DONE 2026-07-20 [T2 startup-verified].**
|
||||
The `-platform glass` preset now breaks the secondary displays out of the single combined
|
||||
panel/strip into ONE desktop window PER pod display, each carrying that display's surface
|
||||
WITH its RIO button bank placed around it, arranged pod-faithfully around the main view
|
||||
(`engine/MUNGA_L4/L4GLASSWIN.*`, new TU). Windows: **Heat (UL) / Mfd2 (UC) / Comm (UR) /
|
||||
Mfd1 (LL) / Mfd3 (LR)** each with 8 RED MFD buttons split 4 above / 4 below the surface;
|
||||
**Secondary/Radar** (portrait `sec`) with the 12 YELLOW Secondary buttons split left/right;
|
||||
a **Flight Controls** window for the no-display banks (throttle 0x38-3F, joystick/fire
|
||||
0x40-47, panic). Each window is a GDI window (like [[gauges-hud]]'s pad panel + the plasma
|
||||
window) — the surface is CPU-expanded from the shared gauge buffer
|
||||
(`SVGA16::ExpandPlaneToBGRA`, no D3D) and `StretchDIBits`'d in, so the **whole D3D
|
||||
dev-composite path (dock / separate window / overlay) stands down** while these are up
|
||||
(`BTDrawGaugeInset`/`BTGaugeWindowRenderAndPresent` early-return; no `gBTGaugeDockBottom`
|
||||
strip). Runtime gate **`BT_GLASS_PANELS`** (default ON under `-platform glass`, `=0` falls
|
||||
back to the single pad panel + docked gauges) — read by `L4PADRIO` (which panel to create),
|
||||
`L4VB16` (suppress compositing), and `btl4main` (skip the world-window dock strip). The
|
||||
display↔bank map (spine of the feature): Heat 0x28-2F, Mfd2 0x20-27, Comm 0x30-37,
|
||||
Mfd1 0x08-0F, Mfd3 0x00-07, sec 0x10-1B ([[pod-hardware]] §Bank→MFD). Verified: build +
|
||||
bounded glass launch — `[glasswin] per-display cockpit up (7 windows…)`, no crash, full
|
||||
mission loop un-regressed; surfaces carry LIVE gauge content (`BT_GLASS_LOG` = per-surface
|
||||
resolve/pixel diagnostic). Layout constants (surface sizes, exact button sub-positions,
|
||||
yellow left/right split) are marked tunable in `L4GLASSWIN.cpp`. Detail: `docs/GLASS_COCKPIT.md`.
|
||||
⚠ **Bug found + fixed during bring-up:** surfaces were blank because `application`/`ghWnd` are
|
||||
duplicate-defined globals that bind non-deterministically under `/FORCE` — the fresh
|
||||
`L4GLASSWIN` TU read `application == NULL` → gauge renderer NULL. Fix: reach the
|
||||
renderer/window via `BTResolveGaugeRenderer()`/`BTResolveMainWindow()`, DEFINED in
|
||||
`game/btl4main.cpp` off its real `btl4App`/`hWnd` pointers (never the globals directly). See
|
||||
[[reconstruction-gotchas]] §6 duplicate-GLOBAL corollary.
|
||||
|
||||
## Key Relationships
|
||||
- Extends: [[pod-hardware]] (RIO input path) · Uses: [[gauges-hud]] dev-gauge modes ·
|
||||
|
||||
@@ -121,6 +121,20 @@ mislanch.cpp's extern but not projweap.cpp's → first AUTOCANNON shot AV'd). **
|
||||
bridge-signature change, `grep -rn "extern .*<name>"` and update every declaration; then grep
|
||||
the fresh link output for the symbol name** — the pre-existing LNK2019 wall camouflages new
|
||||
entries if you only eyeball it.
|
||||
**Duplicate-GLOBAL corollary (glass per-display windows, 2026-07-20):** a global DEFINED in two
|
||||
libs (the 1995 headers declare free globals without `inline`/`extern`, so `application`,
|
||||
`ghWnd`, … exist in BOTH `munga_engine` and `bt410_l4`) links under `/FORCE:MULTIPLE` with
|
||||
per-object binding that is **non-deterministic across links** — a given `.obj` can resolve
|
||||
`application` to the copy the game ASSIGNS (correct) or to the other copy (stays NULL). A
|
||||
NEWLY-ADDED engine TU is the classic victim: it read `application == NULL` forever while
|
||||
`L4VB16.cpp` in the same lib read the assigned pointer, and a relink flipped which one was
|
||||
right. Symptom: a feature that reads an engine global silently no-ops (here: the gauge
|
||||
renderer came back NULL → blank glass surfaces), no crash, no link error. **Rule: from a fresh
|
||||
TU, never touch these duplicate globals directly — resolve through a tiny accessor DEFINED in
|
||||
the TU that OWNS the real pointer** (`game/btl4main.cpp` holds `btl4App`/`hWnd` as its own
|
||||
file-scope pointers; `BTResolveGaugeRenderer()`/`BTResolveMainWindow()` there are always
|
||||
correct). Do NOT "fix" it by adding the accessor to another engine TU — that binding is just as
|
||||
random. [T2]
|
||||
|
||||
## 7. Dtor-epilogue rule — do not reconstruct compiler glue
|
||||
|
||||
|
||||
@@ -285,3 +285,93 @@ cavern.egg`. This is every option the original console exposed for a BattleTech
|
||||
- dropzone (5, the `dropzone=` field): one, two, three, four, five ← was `one`
|
||||
- advancedDamage (0/1): the advanced damage model toggle ← was hardcoded `1`
|
||||
- vehicleValue: 1000 (const), loadzones=1 (const), bitmapindex (per-pilot ordinal)
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-20 — Per-display cockpit windows (BT_GLASS_PANELS)
|
||||
|
||||
**Goal (Cyd):** stop collapsing the desktop cockpit into combined surfaces. Break each
|
||||
SECONDARY display out into its OWN window carrying that display's surface WITH its RIO button
|
||||
bank placed around it, arranged pod-faithfully around the main 3D view — a dev cockpit that
|
||||
reads like the pod, so button/lamp/display behavior can be validated in position.
|
||||
|
||||
**New TU: `engine/MUNGA_L4/L4GLASSWIN.{h,cpp}`** (BT_GLASS-only; added to the CMake BT_GLASS
|
||||
`target_sources`). Builds SEVEN GDI windows and positions them around the main window:
|
||||
|
||||
| Window | Surface (port) | RIO bank | Buttons |
|
||||
|---|---|---|---|
|
||||
| Heat MFD (UL) | `Heat` (0x4000) | 0x28-0x2F | 8 red, 4 above / 4 below |
|
||||
| MFD 2 (UC) | `Mfd2`/`Eng2` (0x0400) | 0x20-0x27 | 8 red, 4 above / 4 below |
|
||||
| Comm MFD (UR) | `Comm` (0x8000) | 0x30-0x37 | 8 red, 4 above / 4 below |
|
||||
| MFD 1 (LL) | `Mfd1`/`Eng1` (0x0100) | 0x08-0x0F | 8 red, 4 above / 4 below |
|
||||
| MFD 3 (LR) | `Mfd3`/`Eng3` (0x1000) | 0x00-0x07 | 8 red, 4 above / 4 below |
|
||||
| Secondary/Radar | `sec` (palette, portrait) | 0x10-0x1B Secondary | 12 yellow, 6 left / 6 right |
|
||||
| Flight Controls | (none) | 0x38-0x47 (+ panic/icom) | neutral, 2 columns |
|
||||
|
||||
**Architecture — GDI + CPU blit, NO D3D for the secondaries.** Each window is a layered,
|
||||
topmost GDI window (the L4PADPANEL pattern: double-buffered paint, ~62 ms repaint timer,
|
||||
left-click = momentary / right-click = latch → `PadRIO::SetScreenButton`; lamps from
|
||||
`PadRIO::GetLampState`). The surface is CPU-expanded from the shared gauge pixelBuffer by the
|
||||
new **`SVGA16::ExpandPlaneToBGRA(mask, paletteID, monoTint, rotateQuadrant, dst, &ow, &oh)`**
|
||||
(L4VB16.cpp) — the no-D3D analog of `DrawDevSurface`: palette-LUT expand for `sec` (monoTint
|
||||
< 0), mono bit-plane → tint for the MFDs; the portrait secondary CRT is unrotated by
|
||||
transposing into `dst` (rotate 1/3, `BT_GAUGE_SEC_ROT`) — then `StretchDIBits` (HALFTONE) into
|
||||
the surface rect. An Mfd/Eng pair shows whichever plane is the live (non-`BlankColor`) one, as
|
||||
on the pod monitor. This reuses the plasma-window CPU→window pattern and sidesteps the whole
|
||||
D3D dev-composite bug family (additional-swap-chain lifetime, device-state save/restore,
|
||||
depth-stencil mismatch, layered strobing).
|
||||
|
||||
**Gate: `BT_GLASS_PANELS`** (runtime env; `BTGlassPanelsActive()`). Default ON under
|
||||
`-platform glass` (preset putenv in `btl4main.cpp`); `=0` restores the legacy single pad panel
|
||||
+ docked gauge strip. Wired at three seams:
|
||||
- `L4PADRIO` ctor: `BTGlassPanelsActive()` → `BTGlassPanels_Create()` instead of
|
||||
`BTPadPanel_Create()`; dtor destroys both (safe no-ops).
|
||||
- `L4VB16`: `BTDrawGaugeInset` / `BTGaugeWindowRenderAndPresent` early-return when
|
||||
`GlassPanelsOwnSurfaces()` (the per-display windows own the surfaces).
|
||||
- `btl4main`: skip the `gBTGaugeDockBottom` + 1100×600 world-window forcing (world stays
|
||||
normal size — no bottom strip).
|
||||
|
||||
**Placement.** `ComputeLayout()` rings the windows around `ghWnd` (the main game window):
|
||||
top row Heat/Mfd2/Comm above it, Mfd1/Mfd3 at the lower corners, radar below-center, Flight
|
||||
Controls left of the seat; clamped to the work area, with an 800×600-centered fallback before
|
||||
the main window exists + a one-shot reposition once `ghWnd` comes up (in the repaint timer).
|
||||
|
||||
**Verified [T2 startup].** `-DBT_GLASS=ON` Debug build links (the usual `/FORCE` mech3
|
||||
subsystem set is pre-existing, unrelated). Bounded `-platform glass -egg DEV.EGG` launch:
|
||||
`[glasswin] per-display cockpit up (7 windows: 5 MFD + radar + flight)`, `[plasmawin] … up`,
|
||||
`[padrio] XInput pad found`, no failure/abort markers, full mission loop un-regressed (killed
|
||||
after 18 s). Visual layout / surface content / click-through NOT yet human-verified.
|
||||
|
||||
**Tunable (Cyd flagged refinements):** surface display sizes, the exact button sub-positions
|
||||
around each screen, the yellow left/right split (first cut 0x10-0x15 / 0x16-0x1B), and the MFD
|
||||
mono phosphor tint — all named constants at the top of `L4GLASSWIN.cpp`. Deferred: folding the
|
||||
plasma window into the same arrangement; per-window draggable/saved positions; moving/resizing
|
||||
the main window into a true center cell for a tighter ring.
|
||||
|
||||
### Blank-surface bug (found + fixed same day) — /FORCE duplicate-global race
|
||||
|
||||
First glass-panels run showed the buttons but BLANK display surfaces. `BT_GLASS_LOG`
|
||||
diagnostics in `BlitSurface` traced it stage by stage: `application == 0x00000000` in
|
||||
`L4GLASSWIN.cpp` for the ENTIRE run (call 1 → 481), so `application->GetGaugeRenderer()` was
|
||||
never even reached. Yet the legacy dock path (`BTDrawGaugeSurfaces`, `L4VB16.cpp`, SAME engine
|
||||
lib) logged `application=047BA9C8 gr=0C57FF78` — non-NULL.
|
||||
|
||||
Root cause: the 1995 headers declare free globals (`application`, `ghWnd`, …) without
|
||||
`inline`/`extern`, so they are DEFINED in both `munga_engine` and `bt410_l4`. Under
|
||||
`/FORCE:MULTIPLE` the linker binds each `.obj`'s reference to one copy or the other
|
||||
**non-deterministically per link** — the newly-added `L4GLASSWIN.obj` bound the copy the game
|
||||
never assigns (stays NULL), and a relink even flipped `L4VB16.obj`. Routing through an engine
|
||||
helper is therefore unreliable (its binding is equally random).
|
||||
|
||||
Fix: `BTResolveGaugeRenderer()` / `BTResolveMainWindow()` (declared in `l4vb16.h`) are DEFINED
|
||||
in `game/btl4main.cpp` off the launcher's OWN file-scope pointers — `btl4App` (set to `new_app`
|
||||
at btl4main.cpp:631) and the global `hWnd` (the real main window). Those live in `btl4main.obj`
|
||||
and are the actual assigned pointers, so the resolvers are always correct regardless of the
|
||||
duplicate-symbol binding. `BlitSurface` and the placement code call the resolvers, never the
|
||||
`application`/`ghWnd` globals. New durable gotcha: `context/reconstruction-gotchas.md` §6
|
||||
duplicate-GLOBAL corollary.
|
||||
|
||||
Verified after the fix (`BT_GLASS_LOG`): `[glass] 'Heat MFD' port=Heat mask=0x4000 pal=3
|
||||
enable=2 ow=640 oh=480 nonzero=22227/307200`, `[glass] 'Secondary / Radar' port=sec mask=0x3f
|
||||
pal=1 enable=3 ow=480 oh=640 nonzero=26206/307200` — surfaces resolve and carry live pixels.
|
||||
`BT_GLASS_LOG` left in as a permanent per-surface resolve/pixel diagnostic (throttled).
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "l4padrio.h"
|
||||
#include "l4padpanel.h"
|
||||
#include "l4glasswin.h"
|
||||
#include "l4ctrl.h"
|
||||
|
||||
#include <windows.h>
|
||||
@@ -168,10 +169,16 @@ PadRIO::PadRIO():
|
||||
<< "live on focus; L4PADFLIP=" << flipStickAxes << ")\n" << std::flush;
|
||||
|
||||
//
|
||||
// The on-screen cockpit button panel (step 2d) rides the device: the
|
||||
// glass preset sets BT_PAD_PANEL=1.
|
||||
// The on-screen cockpit buttons ride the device. BT_GLASS_PANELS (the
|
||||
// glass preset default) breaks each secondary display into its own window
|
||||
// with its RIO bank around it; otherwise the single combined pad panel
|
||||
// (BT_PAD_PANEL=1) is used.
|
||||
//
|
||||
if (getenv("BT_PAD_PANEL") != NULL && *getenv("BT_PAD_PANEL") != '0')
|
||||
if (BTGlassPanelsActive())
|
||||
{
|
||||
BTGlassPanels_Create();
|
||||
}
|
||||
else if (getenv("BT_PAD_PANEL") != NULL && *getenv("BT_PAD_PANEL") != '0')
|
||||
{
|
||||
BTPadPanel_Create();
|
||||
}
|
||||
@@ -179,6 +186,7 @@ PadRIO::PadRIO():
|
||||
|
||||
PadRIO::~PadRIO()
|
||||
{
|
||||
BTGlassPanels_Destroy(); // safe no-op if the glass windows were never created
|
||||
BTPadPanel_Destroy();
|
||||
if (activeInstance == this)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
#include "L4VIDEO.h"
|
||||
#include "DXUtils.h"
|
||||
|
||||
#ifdef BT_GLASS
|
||||
#include "l4glasswin.h" // BTGlassPanelsActive(): the per-display windows own the surfaces
|
||||
#endif
|
||||
// BTResolveGaugeRenderer()/BTResolveMainWindow() (l4vb16.h) are DEFINED in
|
||||
// game/btl4main.cpp off the real app/window pointers -- NOT here: an engine TU
|
||||
// can bind the NULL /FORCE-duplicate copy of `application`/`ghWnd`, and which
|
||||
// copy an obj binds is non-deterministic per link.
|
||||
|
||||
#if defined(TRACE_SCREEN_COPY)
|
||||
static BitTrace Screen_Copy("Screen Copy");
|
||||
#define SET_SCREEN_COPY() Screen_Copy.Set()
|
||||
@@ -153,6 +161,18 @@ static bool DevGaugeDocked()
|
||||
return v != 0 || gBTGaugeDockBottom != 0;
|
||||
}
|
||||
|
||||
// GLASS per-display windows (L4GLASSWIN) own the instrument surfaces in their OWN
|
||||
// GDI windows -- when active, the whole D3D dev-composite path (dock / separate
|
||||
// window / overlay) must stand down so nothing double-draws into the main window.
|
||||
static bool GlassPanelsOwnSurfaces()
|
||||
{
|
||||
#ifdef BT_GLASS
|
||||
return BTGlassPanelsActive() != 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// DEV-COMPOSITE (option B) -- draw the woken gauge renderer's SECONDARY/radar
|
||||
// surface as an inset in the MAIN window. The gauge widgets have already
|
||||
@@ -260,6 +280,89 @@ void SVGA16::DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int pa
|
||||
device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, quad, sizeof(InsetVert));
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// GLASS per-display windows -- the CPU (no-D3D) analog of DrawDevSurface: expand
|
||||
// one bit-plane of the shared gauge pixelBuffer into a 32-bit BGRA image that the
|
||||
// GDI window blits with StretchDIBits (L4GLASSWIN.cpp). The rotation the D3D path
|
||||
// did via texture-UV remap is baked in here by transposing into `dst`.
|
||||
//===========================================================================//
|
||||
void SVGA16::ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotateQuadrant,
|
||||
unsigned long *dst, int *outW, int *outH)
|
||||
{
|
||||
int w = pixelBuffer.Data.Size.x; // 640
|
||||
int h = pixelBuffer.Data.Size.y; // 480
|
||||
if (dst == NULL || w <= 0 || h <= 0)
|
||||
{
|
||||
if (outW) *outW = 0;
|
||||
if (outH) *outH = 0;
|
||||
return;
|
||||
}
|
||||
Word *base = pixelBuffer.Data.MapPointer;
|
||||
SVGA16Palette *pal = &palette[paletteID];
|
||||
|
||||
if (rotateQuadrant == 0)
|
||||
{
|
||||
// Native orientation, top-down straight copy.
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
Word *src = base + y * w;
|
||||
unsigned long *d = dst + y * w;
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
Word s = src[x];
|
||||
if (monoTint < 0)
|
||||
{
|
||||
PaletteTriplet *pe = &(pal->paletteData.Color[s & mask]);
|
||||
d[x] = ((unsigned long)pe->Red << 16) |
|
||||
((unsigned long)pe->Green << 8) | (unsigned long)pe->Blue;
|
||||
}
|
||||
else
|
||||
{
|
||||
d[x] = (s & mask) ? (unsigned long)monoTint : 0UL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (outW) *outW = w;
|
||||
if (outH) *outH = h;
|
||||
return;
|
||||
}
|
||||
|
||||
// 90-degree rotation: output is transposed (ow = h, oh = w). rotate 3 = CW,
|
||||
// rotate 1 = CCW (the DrawDevSurface convention; BT_GAUGE_SEC_ROT picks it).
|
||||
int ow = h, oh = w;
|
||||
for (int oy = 0; oy < oh; oy++)
|
||||
{
|
||||
unsigned long *d = dst + oy * ow;
|
||||
for (int ox = 0; ox < ow; ox++)
|
||||
{
|
||||
int sx, sy;
|
||||
if (rotateQuadrant == 3) // 90 CW: out(ox,oy) = src(oy, h-1-ox)
|
||||
{
|
||||
sx = oy;
|
||||
sy = h - 1 - ox;
|
||||
}
|
||||
else // 90 CCW: out(ox,oy) = src(w-1-oy, ox)
|
||||
{
|
||||
sx = w - 1 - oy;
|
||||
sy = ox;
|
||||
}
|
||||
Word s = base[sy * w + sx];
|
||||
if (monoTint < 0)
|
||||
{
|
||||
PaletteTriplet *pe = &(pal->paletteData.Color[s & mask]);
|
||||
d[ox] = ((unsigned long)pe->Red << 16) |
|
||||
((unsigned long)pe->Green << 8) | (unsigned long)pe->Blue;
|
||||
}
|
||||
else
|
||||
{
|
||||
d[ox] = (s & mask) ? (unsigned long)monoTint : 0UL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (outW) *outW = ow;
|
||||
if (outH) *outH = oh;
|
||||
}
|
||||
|
||||
//
|
||||
// The pod's SIX instrument surfaces, all bit-plane views of the ONE shared 640x480
|
||||
// gauge buffer (masks/positions from content/GAUGE/L4GAUGE.CFG MechInit @4395):
|
||||
@@ -401,6 +504,7 @@ void BTDrawGaugeSurfaces(LPDIRECT3DDEVICE9 device, float px, float py, float pw,
|
||||
void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device)
|
||||
{
|
||||
if (!DevGaugeComposite() || !DevGaugeDocked() || device == NULL) return;
|
||||
if (GlassPanelsOwnSurfaces()) return; // the per-display glass windows draw the surfaces
|
||||
if (gBTGaugeDockBottom)
|
||||
{
|
||||
// DOCK-BOTTOM: the strip owns the bottom band of the target (the world
|
||||
@@ -544,6 +648,7 @@ static bool BTGaugeWindowEnsure(LPDIRECT3DDEVICE9 device)
|
||||
void BTGaugeWindowRenderAndPresent(LPDIRECT3DDEVICE9 device)
|
||||
{
|
||||
if (!DevGaugeComposite() || DevGaugeDocked() || device == NULL) return;
|
||||
if (GlassPanelsOwnSurfaces()) return; // the per-display glass windows draw the surfaces
|
||||
if (!BTGaugeWindowEnsure(device)) return;
|
||||
|
||||
IDirect3DSurface9 *gaugeBB = NULL;
|
||||
|
||||
@@ -7,6 +7,15 @@
|
||||
|
||||
|
||||
class L4graphicsPort;
|
||||
class GaugeRenderer;
|
||||
|
||||
// GLASS: resolve the gauge renderer / main window. DEFINED in game/btl4main.cpp
|
||||
// off the REAL app + window pointers it holds as locals -- NOT off the
|
||||
// `application`/`ghWnd` globals, which are duplicate-defined and bind
|
||||
// non-deterministically per link under /FORCE (the "duplicate-symbol race" --
|
||||
// CMakeLists.txt:186), so a newly-added engine TU (L4GLASSWIN) can read NULL.
|
||||
GaugeRenderer *BTResolveGaugeRenderer();
|
||||
void *BTResolveMainWindow(); // HWND (kept void* -- no windows.h dependency)
|
||||
|
||||
//########################################################################
|
||||
//######################### Video16BitBuffered ###########################
|
||||
@@ -306,6 +315,17 @@ public:
|
||||
void DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int paletteID,
|
||||
int monoTint, float dstX, float dstY, float dstW, float dstH,
|
||||
int rotateCCW = 0);
|
||||
|
||||
// GLASS (per-display windows, L4GLASSWIN): CPU-expand ONE gauge surface (a
|
||||
// bit-plane of the shared pixelBuffer) into a 32-bit BGRA image for GDI
|
||||
// StretchDIBits -- no D3D. Same pixel rule as DrawDevSurface: monoTint < 0 ->
|
||||
// palette-expand via palette[paletteID]; else fill (word & mask) ? monoTint : 0
|
||||
// (monoTint is a 0x00RRGGBB value here, NOT R5G6B5). rotateQuadrant: 0 = native
|
||||
// (outW=bufW, outH=bufH); 1 = 90 CCW / 3 = 90 CW -> transposed (outW=bufH,
|
||||
// outH=bufW), for the portrait secondary CRT. `dst` must hold >= bufW*bufH
|
||||
// dwords; the image is written TOP-DOWN. *outW/*outH receive the produced size.
|
||||
void ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotateQuadrant,
|
||||
unsigned long *dst, int *outW, int *outH);
|
||||
};
|
||||
|
||||
//########################################################################
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
//###########################################################################
|
||||
//
|
||||
// L4GLASSWIN -- per-display glass cockpit windows (BT_GLASS only; this TU is
|
||||
// only in the build when the gate is on -- see CMakeLists.txt).
|
||||
//
|
||||
// Under `-platform glass` with BT_GLASS_PANELS=1 (the glass preset default),
|
||||
// each pod SECONDARY display gets its OWN desktop window carrying that
|
||||
// display's surface AND its RIO button bank placed AROUND it, mirroring the
|
||||
// physical pod cockpit:
|
||||
//
|
||||
// Heat (UL) / Mfd2 (UC) / Comm (UR) / Mfd1 (LL) / Mfd3 (LR) -- red MFD
|
||||
// banks split 4 buttons ABOVE / 4 BELOW the surface;
|
||||
// sec radar (center, portrait) -- yellow
|
||||
// Secondary buttons split LEFT / RIGHT of the surface;
|
||||
// Flight Controls -- the
|
||||
// no-display banks (throttle 0x38-3F, joystick/fire 0x40-47, panic).
|
||||
//
|
||||
// Each window is a GDI window (like L4PADPANEL + L4PLASMAWIN): the surface is
|
||||
// CPU-expanded from the shared gauge pixelBuffer (SVGA16::ExpandPlaneToBGRA)
|
||||
// and StretchDIBits'd in -- NO D3D, so the whole D3D dev-composite path
|
||||
// (dock / separate window / overlay) stands down while these are up. Clicks
|
||||
// inject through PadRIO::SetScreenButton; lamps render from GetLampState.
|
||||
// Created / destroyed by the PadRIO ctor / dtor.
|
||||
//
|
||||
//###########################################################################
|
||||
|
||||
void
|
||||
BTGlassPanels_Create();
|
||||
void
|
||||
BTGlassPanels_Destroy();
|
||||
|
||||
//
|
||||
// True when BT_GLASS_PANELS mode is selected (default ON under `-platform
|
||||
// glass`, OFF otherwise). Read by the render loop / L4VB16 dev-composite to
|
||||
// suppress the single L4PADPANEL and the D3D gauge compositing. Cheap +
|
||||
// cached; safe to call before the windows exist.
|
||||
//
|
||||
int
|
||||
BTGlassPanelsActive();
|
||||
+33
-6
@@ -45,6 +45,22 @@ const char* const ProgName = "btl4";
|
||||
Application *btl4App = NULL;
|
||||
HWND hWnd = NULL;
|
||||
|
||||
// GLASS: resolvers for the per-display windows (declared in l4vb16.h), defined
|
||||
// HERE off the launcher's own app + window pointers. They deliberately do NOT
|
||||
// touch the `application`/`ghWnd` globals: those are duplicate-defined and bind
|
||||
// non-deterministically per link under /FORCE (CMakeLists.txt:186), so a fresh
|
||||
// engine TU (L4GLASSWIN) can read the NULL copy. btl4App/hWnd live in THIS obj
|
||||
// and are the real, assigned pointers -- always correct.
|
||||
class GaugeRenderer;
|
||||
GaugeRenderer *BTResolveGaugeRenderer()
|
||||
{
|
||||
return btl4App ? btl4App->GetGaugeRenderer() : 0;
|
||||
}
|
||||
void *BTResolveMainWindow()
|
||||
{
|
||||
return (void *)hWnd;
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// Bring-up player DRIVE input (Tier 2 locomotion).
|
||||
// Populated by the keyboard handler in WndProc below (WASD / arrow keys)
|
||||
@@ -319,10 +335,14 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
// window / BT_DEV_GAUGES_DOCK=1 overlay inset), and the desktop
|
||||
// plasma window. Each only when not already set, so a developer
|
||||
// env overrides the preset.
|
||||
if (getenv("L4CONTROLS") == NULL) putenv("L4CONTROLS=PAD,KEYBOARD");
|
||||
if (getenv("BT_DEV_GAUGES") == NULL) putenv("BT_DEV_GAUGES=1");
|
||||
if (getenv("L4PLASMA") == NULL) putenv("L4PLASMA=SCREEN");
|
||||
if (getenv("BT_PAD_PANEL") == NULL) putenv("BT_PAD_PANEL=1");
|
||||
if (getenv("L4CONTROLS") == NULL) putenv("L4CONTROLS=PAD,KEYBOARD");
|
||||
if (getenv("BT_DEV_GAUGES") == NULL) putenv("BT_DEV_GAUGES=1");
|
||||
if (getenv("L4PLASMA") == NULL) putenv("L4PLASMA=SCREEN");
|
||||
if (getenv("BT_PAD_PANEL") == NULL) putenv("BT_PAD_PANEL=1");
|
||||
// Break the secondary displays into per-display windows (surface + its RIO
|
||||
// bank around it). Set BT_GLASS_PANELS=0 to fall back to the single
|
||||
// combined pad panel + docked gauge strip.
|
||||
if (getenv("BT_GLASS_PANELS") == NULL) putenv("BT_GLASS_PANELS=1");
|
||||
}
|
||||
#endif
|
||||
else
|
||||
@@ -352,7 +372,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
std::cout << "[boot] platform profile: "
|
||||
<< (fe_menu_mode ? "MENU (front end -- no profile applied)"
|
||||
: gBTPlatformPod ? "POD (RIO cockpit input; multi-surface gauges/MFDs via pod hardware or explicit L4GAUGE)"
|
||||
: gBTPlatformGlass ? "GLASS (PadRIO + on-screen panel + dev MFDs + plasma window)"
|
||||
: gBTPlatformGlass ? "GLASS (PadRIO + per-display cockpit windows [BT_GLASS_PANELS] + plasma window)"
|
||||
: "DEV (single window + keyboard)")
|
||||
<< std::endl << std::flush;
|
||||
|
||||
@@ -543,7 +563,14 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
{
|
||||
extern int gBTGaugeDockBottom;
|
||||
extern int BTGaugeStripHeightFor(int width);
|
||||
if (getenv("BT_DEV_GAUGES") != 0 && getenv("BT_DEV_GAUGES_WINDOW") == 0)
|
||||
// BT_GLASS_PANELS breaks the gauges into their own per-display windows, so
|
||||
// the world window keeps its normal size (no bottom strip).
|
||||
int glassOwnsGauges = 0;
|
||||
#ifdef BT_GLASS
|
||||
{ extern int BTGlassPanelsActive(); glassOwnsGauges = BTGlassPanelsActive(); }
|
||||
#endif
|
||||
if (!glassOwnsGauges &&
|
||||
getenv("BT_DEV_GAUGES") != 0 && getenv("BT_DEV_GAUGES_WINDOW") == 0)
|
||||
{
|
||||
gBTGaugeDockBottom = 1;
|
||||
// Readability default (user-reported: the strip at 800 wide is a
|
||||
|
||||
Reference in New Issue
Block a user