Glass windows remember where you drag them (BT_GLASS_LAYOUT)
The BT_GLASS_PANELS windows self-place in a pod-faithful ring and re-snap
once the main window is up. They carry WS_CAPTION so a dev can drag them,
but the drag never survived the menu->mission->menu relaunch loop.
BT_GLASS_LAYOUT (L4GLASSWIN.cpp) adds opt-in persistence to a cwd-relative
glass_layout.cfg beside bindings.txt (gitignored):
off/0/unset computed ring only, no file I/O (default, pod-faithful)
load restore saved positions on startup (per-window fallback
to computed); never writes
save/adjust restore first, then rewrite on each finished drag
(WM_EXITSIZEMOVE) and on teardown -- the round trip
One "<title>=x,y,w,h" line per window; position restored, size ignored
(frame size is deterministic from content, so an old w,h can't distort a
later geometry change). A restored window is flagged so ComputeLayout's
post-main-window re-snap leaves hand-placed windows alone. Native
analogue of TeslaRel410 pod-launch's per-rig --bridge-pos/--layout args.
Verified [T2 runtime round-trip]: load restored 7 from a seeded cfg;
save wrote all 7 with a window moved to 777,333 (GetWindowRect read the
live window); relaunch in load put that window physically at 777,333
after the re-snap fired. Release links clean (only the 40 tolerated
/FORCE externals).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -113,6 +113,7 @@ struct GWin
|
||||
int clientW, clientH;
|
||||
int frameW, frameH;
|
||||
int wantX, wantY;
|
||||
int restored; // wantX/wantY came from glass_layout.cfg -> don't re-snap
|
||||
};
|
||||
|
||||
static GWin gWins[8];
|
||||
@@ -331,6 +332,8 @@ static void
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
{
|
||||
GWin &w = gWins[i];
|
||||
if (w.restored)
|
||||
continue; // sticky (BT_GLASS_LAYOUT) -- keep the saved spot
|
||||
int fw = w.frameW, fh = w.frameH;
|
||||
int x = m.left, y = m.top;
|
||||
switch (i)
|
||||
@@ -362,6 +365,152 @@ static void
|
||||
0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// Sticky layout -- BT_GLASS_LAYOUT persists each window's on-screen position
|
||||
// to glass_layout.cfg (cwd-relative, beside bindings.txt), so a developer can
|
||||
// drag the ring into place once and have it survive the menu->mission->menu
|
||||
// relaunch loop and future launches. Modes:
|
||||
// off / 0 / unset -- computed pod-faithful ring only, no file I/O (default)
|
||||
// load / restore -- restore saved positions (per-window fallback to
|
||||
// computed); never write
|
||||
// save / adjust / -- restore first, then re-save on finished-drag
|
||||
// on / 1 (WM_EXITSIZEMOVE) and on teardown [the round trip]
|
||||
// Position is restored; size stays deterministic from the window's content, so
|
||||
// a later geometry change can't distort an old save. Windows carry WS_CAPTION
|
||||
// in every mode, so they are always draggable -- the gate only controls whether
|
||||
// a drag STICKS.
|
||||
//###########################################################################
|
||||
|
||||
enum { LayoutOff = 0, LayoutLoad = 1, LayoutSave = 2 };
|
||||
|
||||
static const char *layoutFileName = "glass_layout.cfg";
|
||||
|
||||
static int
|
||||
GlassLayoutMode()
|
||||
{
|
||||
static int m = -1;
|
||||
if (m < 0)
|
||||
{
|
||||
const char *e = getenv("BT_GLASS_LAYOUT");
|
||||
if (e == NULL || e[0] == '\0')
|
||||
m = LayoutOff;
|
||||
else if (_stricmp(e, "off") == 0 || _stricmp(e, "0") == 0)
|
||||
m = LayoutOff;
|
||||
else if (_stricmp(e, "load") == 0 || _stricmp(e, "restore") == 0)
|
||||
m = LayoutLoad;
|
||||
else // save / adjust / on / 1 / anything else truthy
|
||||
m = LayoutSave;
|
||||
if (m != LayoutOff)
|
||||
DEBUG_STREAM << "[glasswin] BT_GLASS_LAYOUT="
|
||||
<< (m == LayoutSave ? "save" : "load") << " (" << layoutFileName
|
||||
<< ")\n" << std::flush;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// Find a window by its (stable) title. Titles carry no '=', so the cfg splits
|
||||
// cleanly on the first '='.
|
||||
static GWin *
|
||||
FindGWinByTitle(const char *title)
|
||||
{
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
if (gWins[i].title != NULL && strcmp(gWins[i].title, title) == 0)
|
||||
return &gWins[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 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
|
||||
// re-runs ComputeLayout once the main window appears) leaves them alone.
|
||||
static void
|
||||
LoadLayout()
|
||||
{
|
||||
if (GlassLayoutMode() == LayoutOff)
|
||||
return;
|
||||
|
||||
FILE *f = fopen(layoutFileName, "rt");
|
||||
if (f == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "[glasswin] no " << layoutFileName
|
||||
<< " yet (using computed placement)\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
int restored = 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';
|
||||
// trim trailing space off the title
|
||||
char *end = eq;
|
||||
while (end > s && (end[-1] == ' ' || end[-1] == '\t')) --end;
|
||||
*end = '\0';
|
||||
|
||||
int x = 0, y = 0, w = 0, h = 0;
|
||||
if (sscanf(eq + 1, "%d,%d,%d,%d", &x, &y, &w, &h) < 2)
|
||||
continue;
|
||||
|
||||
GWin *gw = FindGWinByTitle(s);
|
||||
if (gw == NULL)
|
||||
continue;
|
||||
gw->wantX = x;
|
||||
gw->wantY = y;
|
||||
gw->restored = 1;
|
||||
++restored;
|
||||
}
|
||||
fclose(f);
|
||||
DEBUG_STREAM << "[glasswin] restored " << restored << " window position(s) from "
|
||||
<< layoutFileName << "\n" << std::flush;
|
||||
}
|
||||
|
||||
// Write every window's current on-screen frame rect. Whole-file rewrite (it is
|
||||
// tiny), so partial/hard kills never leave a half-written file for long. Called
|
||||
// on finished-drag and on teardown in save mode.
|
||||
static void
|
||||
SaveLayout()
|
||||
{
|
||||
if (GlassLayoutMode() != LayoutSave)
|
||||
return;
|
||||
|
||||
FILE *f = fopen(layoutFileName, "wt");
|
||||
if (f == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "[glasswin] could not write " << layoutFileName
|
||||
<< "\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
fputs("# BT411 glass cockpit window layout (BT_GLASS_LAYOUT=save writes this\n"
|
||||
"# on finished-drag/exit; =load restores it). <title>=<x>,<y>,<w>,<h>\n",
|
||||
f);
|
||||
int wrote = 0;
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
{
|
||||
GWin &gw = gWins[i];
|
||||
if (gw.hwnd == NULL || !IsWindow(gw.hwnd))
|
||||
continue;
|
||||
RECT r;
|
||||
if (!GetWindowRect(gw.hwnd, &r))
|
||||
continue;
|
||||
fprintf(f, "%s=%ld,%ld,%ld,%ld\n", gw.title,
|
||||
(long)r.left, (long)r.top,
|
||||
(long)(r.right - r.left), (long)(r.bottom - r.top));
|
||||
++wrote;
|
||||
}
|
||||
fclose(f);
|
||||
DEBUG_STREAM << "[glasswin] saved " << wrote << " window position(s) to "
|
||||
<< layoutFileName << "\n" << std::flush;
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// Painting
|
||||
//###########################################################################
|
||||
@@ -715,6 +864,13 @@ static LRESULT CALLBACK
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_EXITSIZEMOVE:
|
||||
// The user finished dragging (or resizing) a window. In save/adjust
|
||||
// mode, persist the whole ring so the layout survives a hard kill even
|
||||
// if teardown never runs. No-op in load/off mode.
|
||||
SaveLayout();
|
||||
return 0;
|
||||
|
||||
case WM_CLOSE:
|
||||
ShowWindow(window, SW_HIDE); // hide; the device owns the lifetime
|
||||
return 0;
|
||||
@@ -781,6 +937,7 @@ void
|
||||
gWins[i].frameH = fr.bottom - fr.top;
|
||||
}
|
||||
ComputeLayout();
|
||||
LoadLayout(); // BT_GLASS_LAYOUT=load/save: restore saved positions over the ring
|
||||
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
{
|
||||
@@ -816,6 +973,9 @@ void
|
||||
void
|
||||
BTGlassPanels_Destroy()
|
||||
{
|
||||
SaveLayout(); // backstop for a clean teardown (WM_EXITSIZEMOVE already
|
||||
// caught every finished drag); no-op unless mode==save
|
||||
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
{
|
||||
if (gWins[i].hwnd != NULL)
|
||||
|
||||
Reference in New Issue
Block a user