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:
@@ -30,6 +30,8 @@ content/environ.ini
|
||||
content/frontend.egg
|
||||
content/steam_appid.txt
|
||||
content/fe_last.ini
|
||||
content/glass_layout.cfg
|
||||
glass_layout.cfg
|
||||
# local root copies of the player bats (players/ is the source of truth)
|
||||
/play_solo.bat
|
||||
/joyconfig.bat
|
||||
|
||||
@@ -114,6 +114,31 @@ each, so they cover the foot band too, and the hit test takes the first match
|
||||
what keeps 0x16/0x17/0x1F/0x1E reachable. That class of bug is why the verification below tests
|
||||
first-hit reachability, not mere presence.
|
||||
|
||||
## Sticky window placement — BT_GLASS_LAYOUT (2026-07-28) [T2 round-trip-verified]
|
||||
|
||||
The `BT_GLASS_PANELS` windows self-place in a pod-faithful ring around the main window
|
||||
(`ComputeLayout`, then a one-shot re-snap once the real main window is up). They carry
|
||||
`WS_CAPTION` in every mode, so a dev can always drag them — but by default a drag does **not**
|
||||
survive relaunch (and the menu→mission→menu loop relaunches the process each hop). **`BT_GLASS_LAYOUT`**
|
||||
(`L4GLASSWIN.cpp`) adds opt-in persistence to a cwd-relative `glass_layout.cfg` (beside
|
||||
`bindings.txt`; gitignored):
|
||||
|
||||
| `BT_GLASS_LAYOUT` | Behaviour |
|
||||
|---|---|
|
||||
| unset / `off` / `0` | computed ring only, no file I/O (**default** — pod-faithful, unchanged) |
|
||||
| `load` / `restore` | restore saved positions on startup (per-window fallback to computed for any not in the file); never writes |
|
||||
| `save` / `adjust` / `on` / `1` | restore first, **then** re-write the whole file on each finished drag (`WM_EXITSIZEMOVE`) and on teardown — the round-trip "adjust" mode |
|
||||
|
||||
Mechanics: `glass_layout.cfg` is `# comment`-tolerant, one `«window title»=x,y,w,h` line per
|
||||
window (title is the stable key — titles carry no `=`). **Position is restored; size is not** —
|
||||
each window's frame size is deterministic from its content (`AdjustWindowRect`), so honouring an
|
||||
old saved `w,h` could distort a later geometry change; the `w,h` are written for reference only.
|
||||
A restored window sets a `restored` flag that makes `ComputeLayout` skip it, so the post-main-window
|
||||
re-snap leaves hand-placed windows alone (round-trip verified: a window saved at 777,333 came back
|
||||
at exactly 777,333 after the re-snap fired). Save fires on `WM_EXITSIZEMOVE` (survives a hard kill,
|
||||
since it writes as soon as a drag finishes) with a teardown write as backstop. This is the native
|
||||
analogue of TeslaRel410 `emulator/pod-launch`'s per-rig `--bridge-pos`/`--layout` placement args.
|
||||
|
||||
**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
|
||||
|
||||
@@ -732,3 +732,35 @@ mode). mkdist wraps the fragment in a doctype/charset shell and ships it beside
|
||||
- 72/72 panel addresses still dispatch on click.
|
||||
- surround / exploded / dock / pod / dev: boot + simulate, zero faults.
|
||||
- Dist carries CONTROLS.txt + a 46 KB CONTROLS.html with doctype and charset.
|
||||
|
||||
## 2026-07-28 — Sticky per-display window placement (BT_GLASS_LAYOUT)
|
||||
|
||||
The exploded (`BT_GLASS_PANELS`) windows self-place in the pod-faithful ring and re-snap once the
|
||||
main window appears; a dev could always drag them (they carry `WS_CAPTION`) but the drag never
|
||||
survived the menu→mission→menu relaunch loop. New **`BT_GLASS_LAYOUT`** gate (all in
|
||||
`L4GLASSWIN.cpp`, ~160 lines, no shared-file churn) persists positions to a cwd-relative
|
||||
`glass_layout.cfg` beside `bindings.txt` (gitignored):
|
||||
|
||||
- **`off`/`0`/unset (default):** computed ring only, no file I/O — pod behaviour unchanged.
|
||||
- **`load`/`restore`:** on `Create`, after `ComputeLayout`, `LoadLayout` overrides `wantX/wantY`
|
||||
for each window named in the file (per-window fallback to computed) and sets a `restored` flag;
|
||||
never writes.
|
||||
- **`save`/`adjust`/`on`/`1`:** loads first, then `SaveLayout` rewrites the whole file on each
|
||||
finished drag (`WM_EXITSIZEMOVE` in the WndProc) and once more on teardown (`Destroy`) as a
|
||||
backstop. Writing on drag-finish means a hard kill still keeps the layout.
|
||||
|
||||
Format: `# comment`-tolerant, one `«title»=x,y,w,h` per window; title is the stable key (no `=` in
|
||||
any title). **Position restored, size ignored** — frame size is deterministic from content
|
||||
(`AdjustWindowRect`), so an old `w,h` can't distort a later geometry change; `w,h` are written for
|
||||
reference only. The `restored` flag makes `ComputeLayout` skip that window, so the post-main-window
|
||||
re-snap leaves hand-placed windows put. Native analogue of TeslaRel410 `emulator/pod-launch`'s
|
||||
per-rig `--bridge-pos`/`--layout` args.
|
||||
|
||||
### Verified [T2, runtime round-trip]
|
||||
- Build: Release links clean, only the 40 tolerated `/FORCE` externals, no compile errors.
|
||||
- `load`: seeded a distinctive cfg → log `restored 7 window position(s)`, cockpit up, mission ran.
|
||||
- `save`: moved *Flight Controls* to (777,333) + drove `WM_EXITSIZEMOVE` → cfg written with all 7
|
||||
rects, the moved one at `777,333` (proves `GetWindowRect` read the live window), others at ring.
|
||||
- round-trip: relaunched `load` against the game-written cfg → after 12 s (past the re-snap) the
|
||||
*Flight Controls* window was physically at (777,333), all 7 at their saved coords — the re-snap
|
||||
guard held.
|
||||
|
||||
@@ -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