Merge Cyd's glass-lamp-latency: #138 unfocused flash-rate fix + red Panic/Eject + plasma no-frame
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -169,6 +169,19 @@ Verified live: two flagged windows came up caption-less while the other five kep
|
||||
still dispatched on the frameless radar (`CLICK 'Secondary / Radar' addr=0x18`), and a
|
||||
finished-drag save round-tripped both flags back into the file.
|
||||
|
||||
**The plasma window is in the list too (2026-08-04) [T2 round-trip-verified].** The desktop plasma
|
||||
display (`L4PLASMAWIN`, title `BattleTech - Plasma`) is created by a DIFFERENT TU than the
|
||||
per-display panels, so it can't be a `gWins[]` entry. Instead it registers with a small
|
||||
extern-window registry in `L4GLASSWIN` (`BTGlassLayout_RegisterExtern`), and on creation it reads
|
||||
its saved rect + `,noframe` via `BTGlassLayout_QueryWindow` (BEFORE sizing — `WS_POPUP`'s frame
|
||||
extent differs). `SaveLayout` then writes the plasma's line alongside the panels (with a
|
||||
last-known-rect cache so a teardown before the save still preserves its line), and the plasma
|
||||
WndProc calls `BTGlassLayout_Save` on `WM_EXITSIZEMOVE`/teardown. So `BattleTech - Plasma=x,y,w,h`
|
||||
appears in the cfg like any panel and honours `,noframe`. Verified: a drag wrote
|
||||
`BattleTech - Plasma=321,222,…`; a reload with `,noframe` brought it up `WS_POPUP` (no `WS_CAPTION`)
|
||||
at 321,222. NB the plasma window blits directly every frame (`GetDC`+`StretchDIBits`), so unlike
|
||||
the panels it has no `WM_TIMER` focus-throttle to worry about.
|
||||
|
||||
**Verified 2026-07-26 [T2]:** `BT_RIOBANK_LOG=1` dumps every bank's rects;
|
||||
`scratchpad/checkbank.py` reports the per-bank census and proves no address is SHADOWED (has a
|
||||
point no earlier button covers); `scratchpad/clickbank.py` then posts a real click at every
|
||||
|
||||
@@ -190,7 +190,9 @@ static void
|
||||
GButton &b = w.buttons[w.buttonCount++];
|
||||
b.address = src.address;
|
||||
b.color = (src.colorClass == 1) ? ClrYellow
|
||||
: (src.colorClass == 2) ? ClrBlue : color;
|
||||
: (src.colorClass == 2) ? ClrBlue
|
||||
: (src.colorClass == 0) ? ClrRed // 0 = red (MFD, and the red Panic/Eject 0x3D)
|
||||
: color;
|
||||
b.rect.left = src.x + dx;
|
||||
b.rect.top = src.y + dy;
|
||||
b.rect.right = src.x + dx + src.w;
|
||||
@@ -449,6 +451,91 @@ static GWin *
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// External windows that ride glass_layout.cfg (2026-08-04). A window created
|
||||
// by ANOTHER TU -- the plasma window (L4PLASMAWIN) -- can register here so it
|
||||
// shares the SAME position + ",noframe" persistence as the per-display windows:
|
||||
// it queries its saved rect/flag on creation, and Save() writes its line too.
|
||||
// Tiny (HWND + title + last-known rect); no dynamic state.
|
||||
//###########################################################################
|
||||
|
||||
struct ExternWin
|
||||
{
|
||||
HWND hwnd;
|
||||
char title[64];
|
||||
int noFrame;
|
||||
RECT last;
|
||||
int haveLast;
|
||||
};
|
||||
static ExternWin gExtern[4];
|
||||
static int gExternCount = 0;
|
||||
|
||||
// Read the saved rect + ",noframe" flag for a title (glass window OR extern).
|
||||
// Returns 1 if the title has a line in the cfg. rect / noframe may be NULL.
|
||||
int
|
||||
BTGlassLayout_QueryWindow(const char *title, RECT *rect, int *noframe)
|
||||
{
|
||||
if (title == NULL || GlassLayoutMode() == LayoutOff)
|
||||
return 0;
|
||||
FILE *f = fopen(layoutFileName, "rt");
|
||||
if (f == NULL)
|
||||
return 0;
|
||||
|
||||
int found = 0;
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), f) != NULL)
|
||||
{
|
||||
char *s = line;
|
||||
while (*s == ' ' || *s == '\t') ++s;
|
||||
if (*s == '#' || *s == '\r' || *s == '\n' || *s == '\0')
|
||||
continue;
|
||||
char *eq = strchr(s, '=');
|
||||
if (eq == NULL)
|
||||
continue;
|
||||
*eq = '\0';
|
||||
char *end = eq;
|
||||
while (end > s && (end[-1] == ' ' || end[-1] == '\t')) --end;
|
||||
*end = '\0';
|
||||
if (strcmp(s, title) != 0)
|
||||
continue;
|
||||
|
||||
int x = 0, y = 0, w = 0, h = 0;
|
||||
if (sscanf(eq + 1, "%d,%d,%d,%d", &x, &y, &w, &h) < 2)
|
||||
continue;
|
||||
int nf = 0;
|
||||
for (const char *opt = strchr(eq + 1, ','); opt != NULL; opt = strchr(opt + 1, ','))
|
||||
{
|
||||
const char *t = opt + 1;
|
||||
while (*t == ' ' || *t == '\t') ++t;
|
||||
if (_strnicmp(t, "noframe", 7) == 0) { nf = 1; break; }
|
||||
}
|
||||
if (rect) { rect->left = x; rect->top = y; rect->right = x + w; rect->bottom = y + h; }
|
||||
if (noframe) *noframe = nf;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
fclose(f);
|
||||
return found;
|
||||
}
|
||||
|
||||
// Register an externally-created window so Save() writes its line. Loads its
|
||||
// current ",noframe" flag from the cfg so a save round-trips the flag.
|
||||
void
|
||||
BTGlassLayout_RegisterExtern(HWND hwnd, const char *title)
|
||||
{
|
||||
if (hwnd == NULL || title == NULL)
|
||||
return;
|
||||
if (gExternCount >= (int)(sizeof(gExtern) / sizeof(gExtern[0])))
|
||||
return;
|
||||
ExternWin &e = gExtern[gExternCount++];
|
||||
e.hwnd = hwnd;
|
||||
strncpy(e.title, title, sizeof(e.title) - 1);
|
||||
e.title[sizeof(e.title) - 1] = '\0';
|
||||
e.noFrame = 0;
|
||||
e.haveLast = 0;
|
||||
BTGlassLayout_QueryWindow(title, NULL, &e.noFrame);
|
||||
}
|
||||
|
||||
// Restore saved positions over the just-computed pod-faithful defaults. Called
|
||||
// from Create AFTER ComputeLayout, so any window not named in the file keeps its
|
||||
// computed spot. Restored windows are flagged so the WM_TIMER re-snap (which
|
||||
@@ -545,7 +632,8 @@ static void
|
||||
"# Heat MFD=1920,0,657,539,noframe\n"
|
||||
"# A frameless window is also PINNED (no caption = nothing to drag it\n"
|
||||
"# by), so do the arranging first. Delete the flag to get the frame\n"
|
||||
"# back. Saves preserve it.\n",
|
||||
"# back. Saves preserve it. The 'BattleTech - Plasma' window is in\n"
|
||||
"# this list too -- it can be dragged, remembered and set ,noframe.\n",
|
||||
f);
|
||||
int wrote = 0;
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
@@ -562,11 +650,41 @@ static void
|
||||
gw.noFrame ? ",noframe" : ""); // keep the hand-added option
|
||||
++wrote;
|
||||
}
|
||||
// External windows (the plasma window) ride the same file. Cache the last
|
||||
// good rect so a window torn down before this save still keeps its line.
|
||||
for (int i = 0; i < gExternCount; ++i)
|
||||
{
|
||||
ExternWin &e = gExtern[i];
|
||||
RECT r;
|
||||
if (e.hwnd != NULL && IsWindow(e.hwnd) && GetWindowRect(e.hwnd, &r))
|
||||
{
|
||||
e.last = r; e.haveLast = 1;
|
||||
}
|
||||
else if (e.haveLast)
|
||||
{
|
||||
r = e.last; // window gone -> preserve its last-known line
|
||||
}
|
||||
else
|
||||
continue;
|
||||
fprintf(f, "%s=%ld,%ld,%ld,%ld%s\n", e.title,
|
||||
(long)r.left, (long)r.top,
|
||||
(long)(r.right - r.left), (long)(r.bottom - r.top),
|
||||
e.noFrame ? ",noframe" : "");
|
||||
++wrote;
|
||||
}
|
||||
fclose(f);
|
||||
DEBUG_STREAM << "[glasswin] saved " << wrote << " window position(s) to "
|
||||
<< layoutFileName << "\n" << std::flush;
|
||||
}
|
||||
|
||||
// Public save trigger for registered external windows -- their WndProc calls
|
||||
// this on WM_EXITSIZEMOVE / teardown. No-op unless BT_GLASS_LAYOUT=save.
|
||||
void
|
||||
BTGlassLayout_Save()
|
||||
{
|
||||
SaveLayout();
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// Painting
|
||||
//###########################################################################
|
||||
@@ -1054,3 +1172,43 @@ void
|
||||
if (subFont) { DeleteObject(subFont); subFont = NULL; }
|
||||
if (gStage) { delete[] gStage; gStage = NULL; }
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// Per-frame repaint pump (2026-08-04, "indicators flash slowly unless focused").
|
||||
//
|
||||
// These windows repaint off a 62 ms WM_TIMER. Windows COALESCES/THROTTLES timer
|
||||
// AND paint messages for a window that is in the BACKGROUND (not focused) -- so
|
||||
// when the game window (or another app) holds focus, the panel's WM_TIMER slows
|
||||
// to a crawl and the lamp FLASH (a repaint-driven animation: BTLampBrightnessOf
|
||||
// alternates the shade off GetTickCount every paint) drags. Giving the panel
|
||||
// focus un-throttles its timer, which is exactly what playtesters saw.
|
||||
//
|
||||
// Fix: drive the repaint from the MAIN render loop (BTGlassPanels_Tick, called
|
||||
// once per frame from L4VIDEO), gated to the same ~16 Hz flash cadence. The main
|
||||
// loop runs every frame while the game is up regardless of which window has focus,
|
||||
// and InvalidateRect + UpdateWindow forces a SYNCHRONOUS WM_PAINT -- bypassing the
|
||||
// throttled timer-message path entirely. The WM_TIMER stays (it still owns the
|
||||
// one-shot re-snap); a focused window just repaints from whichever fires first.
|
||||
//###########################################################################
|
||||
|
||||
void
|
||||
BTGlassPanels_Tick()
|
||||
{
|
||||
if (gWinCount == 0)
|
||||
return;
|
||||
|
||||
static unsigned long sLastPaint = 0;
|
||||
unsigned long now = GetTickCount();
|
||||
if (now - sLastPaint < (unsigned long)RepaintMilliseconds)
|
||||
return;
|
||||
sLastPaint = now;
|
||||
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
{
|
||||
if (gWins[i].hwnd != NULL)
|
||||
{
|
||||
InvalidateRect(gWins[i].hwnd, NULL, FALSE);
|
||||
UpdateWindow(gWins[i].hwnd); // synchronous paint, not the throttled queue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ static LRESULT CALLBACK
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case WM_EXITSIZEMOVE:
|
||||
// Finished dragging (framed mode): persist the new position so it sticks
|
||||
// -- same contract as the per-display panels. No-op unless save mode.
|
||||
{ extern void BTGlassLayout_Save(); BTGlassLayout_Save(); }
|
||||
return 0;
|
||||
|
||||
case WM_CLOSE:
|
||||
ShowWindow(window, SW_HIDE); // hide only; the renderer owns it
|
||||
return 0;
|
||||
@@ -52,6 +58,7 @@ PlasmaWindow::~PlasmaWindow()
|
||||
{
|
||||
if (window != NULL)
|
||||
{
|
||||
{ extern void BTGlassLayout_Save(); BTGlassLayout_Save(); } // backstop before the HWND goes
|
||||
DestroyWindow((HWND)window);
|
||||
window = NULL;
|
||||
}
|
||||
@@ -75,20 +82,43 @@ void
|
||||
window_class.lpszClassName = L"BTPlasmaWnd";
|
||||
RegisterClassW(&window_class);
|
||||
|
||||
RECT frame = { 0, 0, plasmaWidth * scale, plasmaHeight * scale };
|
||||
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
|
||||
AdjustWindowRect(&frame, style, FALSE);
|
||||
// glass_layout.cfg integration (2026-08-04): the plasma window rides the same
|
||||
// file as the per-display panels, so it can be dragged-and-remembered and set
|
||||
// frameless with ",noframe" -- BT_GLASS_LAYOUT selects load/save. Read its
|
||||
// saved rect + flag BEFORE sizing (WS_POPUP has a different frame extent than
|
||||
// the framed tool window).
|
||||
extern int BTGlassLayout_QueryWindow(const char *title, RECT *rect, int *noframe);
|
||||
extern void BTGlassLayout_RegisterExtern(HWND hwnd, const char *title);
|
||||
RECT saved;
|
||||
int noframe = 0;
|
||||
int have_saved = BTGlassLayout_QueryWindow("BattleTech - Plasma", &saved, &noframe);
|
||||
|
||||
//
|
||||
// Bottom-right, TOPMOST -- under the (topmost, right-parked) button
|
||||
// panel, clear of the game window (which would otherwise bury it).
|
||||
//
|
||||
DWORD style = noframe
|
||||
? (DWORD)WS_POPUP // bare + pinned, same as a ",noframe" glass panel
|
||||
: (DWORD)(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
|
||||
|
||||
RECT frame = { 0, 0, plasmaWidth * scale, plasmaHeight * scale };
|
||||
AdjustWindowRect(&frame, style, FALSE);
|
||||
int frame_width = frame.right - frame.left;
|
||||
int frame_height = frame.bottom - frame.top;
|
||||
int plasma_x = GetSystemMetrics(SM_CXSCREEN) - frame_width - 12;
|
||||
int plasma_y = GetSystemMetrics(SM_CYSCREEN) - frame_height - 60;
|
||||
if (plasma_x < 0) plasma_x = 0;
|
||||
if (plasma_y < 0) plasma_y = 0;
|
||||
|
||||
int plasma_x, plasma_y;
|
||||
if (have_saved)
|
||||
{
|
||||
plasma_x = saved.left; // position restored; size stays native
|
||||
plasma_y = saved.top;
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Default: bottom-right, TOPMOST -- under the (topmost, right-parked)
|
||||
// button panel, clear of the game window (which would otherwise bury it).
|
||||
//
|
||||
plasma_x = GetSystemMetrics(SM_CXSCREEN) - frame_width - 12;
|
||||
plasma_y = GetSystemMetrics(SM_CYSCREEN) - frame_height - 60;
|
||||
if (plasma_x < 0) plasma_x = 0;
|
||||
if (plasma_y < 0) plasma_y = 0;
|
||||
}
|
||||
|
||||
window = CreateWindowExW(
|
||||
WS_EX_TOPMOST,
|
||||
@@ -102,6 +132,7 @@ void
|
||||
DEBUG_STREAM << "[plasmawin] CreateWindow failed\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
BTGlassLayout_RegisterExtern((HWND)window, "BattleTech - Plasma");
|
||||
ShowWindow((HWND)window, SW_SHOWNOACTIVATE);
|
||||
}
|
||||
|
||||
|
||||
@@ -258,7 +258,10 @@ void
|
||||
int row = i / columns;
|
||||
int x = originX + column * (cw + gap);
|
||||
int y = originY + row * (ch + gap);
|
||||
Push(out, baseAddress + i, x, y, cw, ch, 2,
|
||||
// Flight blocks are blue (colorClass 2), EXCEPT the Panic/Eject at 0x3D:
|
||||
// red (colorClass 0) so the eject stands out on the blue panel.
|
||||
int cc = (baseAddress + i == 0x3D) ? 0 : 2;
|
||||
Push(out, baseAddress + i, x, y, cw, ch, cc,
|
||||
(inert != 0) ? inert[i] : 0,
|
||||
(labels != 0) ? labels[i] : 0);
|
||||
GrowBounds(out, x, y, cw, ch);
|
||||
|
||||
@@ -8960,6 +8960,17 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
extern void BTGaugeWindowRenderAndPresent(LPDIRECT3DDEVICE9 device);
|
||||
BTGaugeWindowRenderAndPresent(mDevice);
|
||||
|
||||
#ifdef BT_GLASS
|
||||
// Drive the per-display glass panels' repaint from the (always-running) main
|
||||
// loop so their lamp flash keeps animating when they're in the background --
|
||||
// Windows throttles an unfocused window's own WM_TIMER ("indicators flash
|
||||
// slowly unless you focus that window"). No-op unless the panels are up.
|
||||
{
|
||||
extern void BTGlassPanels_Tick();
|
||||
BTGlassPanels_Tick();
|
||||
}
|
||||
#endif
|
||||
|
||||
// DIAG (turn-hitch hunt): draw CPU is _rt0..here; Present blocks on the GPU.
|
||||
LARGE_INTEGER _rt1; QueryPerformanceCounter(&_rt1);
|
||||
// COCKPIT LETTERBOX: uniform-scale the canvas into the client (NULL = the
|
||||
|
||||
@@ -31,6 +31,16 @@ void
|
||||
void
|
||||
BTGlassPanels_Destroy();
|
||||
|
||||
//
|
||||
// Per-frame repaint pump. Call once per frame from the main render loop so the
|
||||
// per-display windows' lamp flash keeps animating even when they are in the
|
||||
// background (Windows throttles a background window's own WM_TIMER; this drives a
|
||||
// synchronous repaint at the flash cadence instead). No-op unless the windows
|
||||
// are up. See the note at the definition (L4GLASSWIN.cpp).
|
||||
//
|
||||
void
|
||||
BTGlassPanels_Tick();
|
||||
|
||||
//
|
||||
// True when BT_GLASS_PANELS mode is selected (default ON under `-platform
|
||||
// glass`, OFF otherwise). Read by the render loop / L4VB16 dev-composite to
|
||||
|
||||
Reference in New Issue
Block a user