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:
Joe DiPrima
2026-08-06 09:48:21 -05:00
co-authored by Claude Fable 5
6 changed files with 240 additions and 14 deletions
+160 -2
View File
@@ -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
}
}
}