Merge remote-tracking branch 'origin/glass-cockpit-refit'
This commit is contained in:
@@ -139,6 +139,36 @@ at exactly 777,333 after the re-snap fired). Save fires on `WM_EXITSIZEMOVE` (su
|
||||
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.
|
||||
|
||||
### `,noframe` — per-window chrome removal (2026-07-29) [T2 live]
|
||||
|
||||
Append it to a window's line and that window comes up with no title bar or border (`WS_POPUP`) —
|
||||
for a multi-monitor wall where the chrome is just noise:
|
||||
|
||||
```
|
||||
Heat MFD=1920,0,657,539,noframe
|
||||
```
|
||||
|
||||
**The workflow is deliberately two-stage, and this is the last step:** run with
|
||||
`BT_GLASS_LAYOUT=save` and drag the windows where you want them (they have title bars, so it is
|
||||
the normal sticky-layout flow) → quit → hand-edit the cfg and append `,noframe` to the ones you
|
||||
want bare. From then on those windows are frameless **and pinned** — with no caption there is
|
||||
nothing to drag them by, which is the point: the arrangement is finished. Delete the flag to get
|
||||
the frame back.
|
||||
|
||||
Options are comma-separated after the numbers and **unknown ones are ignored**, so an older build
|
||||
reading a newer file loses the option but never the line — the `bindings.txt` grammar rule
|
||||
applied here.
|
||||
|
||||
The one thing it must not do is eat itself: **`SaveLayout` rewrites the WHOLE file** (any later
|
||||
drag of a framed window triggers it), so it writes the flag back for every window carrying it.
|
||||
And the flag is read BEFORE frame sizing (a `LoadLayout(quiet)` pre-pass), because
|
||||
`AdjustWindowRect` — and therefore `ComputeLayout`'s ring placement — depends on whether a window
|
||||
carries chrome.
|
||||
|
||||
Verified live: two flagged windows came up caption-less while the other five kept theirs, a click
|
||||
still dispatched on the frameless radar (`CLICK 'Secondary / Radar' addr=0x18`), and a
|
||||
finished-drag save round-tripped both flags back into the file.
|
||||
|
||||
**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
|
||||
|
||||
@@ -764,3 +764,45 @@ per-rig `--bridge-pos`/`--layout` args.
|
||||
- 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.
|
||||
|
||||
## 2026-07-29 — glass_layout.cfg gains a per-line ",noframe" option
|
||||
|
||||
Requested: strip a window's title bar from the layout file. Appending `,noframe` to a window's
|
||||
line makes that window a bare `WS_POPUP` -- no caption, no border, nothing but the display and its
|
||||
buttons -- for a multi-monitor wall where the chrome is only noise.
|
||||
|
||||
Heat MFD=1920,0,657,539,noframe
|
||||
|
||||
Options are comma-separated after the four numbers and unknown ones are IGNORED, so an older build
|
||||
reading a newer file loses the option but never the line (the bindings.txt grammar rule applied
|
||||
here). Hand-edited; the file is otherwise machine-written.
|
||||
|
||||
### The workflow is two-stage, on purpose
|
||||
Arrange FIRST, strip chrome LAST:
|
||||
1. run with `BT_GLASS_LAYOUT=save` and drag the windows where you want them (they have title
|
||||
bars -- this is just the normal sticky-layout flow),
|
||||
2. quit,
|
||||
3. hand-edit the cfg and append `,noframe` to the ones you want bare.
|
||||
|
||||
From then on those windows come up frameless AND PINNED -- with no caption there is nothing to
|
||||
drag them by, which is the point: the arrangement is finished. Delete the flag to re-frame.
|
||||
|
||||
(An earlier cut made noframe windows draggable by their surface via `WM_NCHITTEST`->`HTCAPTION`.
|
||||
Removed at the author's direction: it is not an interactive mode, it is the finishing step.)
|
||||
|
||||
The one thing it must not do is eat itself: **SaveLayout rewrites the WHOLE file** -- any later
|
||||
drag of a FRAMED window triggers it -- so it writes the flag back for every window that carries
|
||||
one. Without that, the first drag after adding the option would silently strip it.
|
||||
|
||||
### Ordering
|
||||
The flag is read BEFORE frame sizing (`LoadLayout(quiet)` pre-pass, then the normal pass after
|
||||
`ComputeLayout`): `AdjustWindowRect` -- and therefore the ring placement -- depends on whether a
|
||||
window carries chrome. The pre-pass is quiet so the restore still logs exactly once.
|
||||
|
||||
### Verified live
|
||||
- Two flagged windows (`Heat MFD`, `Secondary / Radar`) came up with `WS_CAPTION` CLEAR while the
|
||||
other five kept it -- read back out of the live windows with `GetWindowLong(GWL_STYLE)`.
|
||||
- A click still dispatched on the frameless radar: `[glasswin] CLICK 'Secondary / Radar' addr=0x18`.
|
||||
- A finished-drag save (`WM_EXITSIZEMOVE`) round-tripped BOTH flags back into the rewritten file,
|
||||
header comment and all.
|
||||
- surround / exploded / layout=off / pod / dev: boot + simulate, zero faults.
|
||||
|
||||
@@ -114,6 +114,7 @@ struct GWin
|
||||
int frameW, frameH;
|
||||
int wantX, wantY;
|
||||
int restored; // wantX/wantY came from glass_layout.cfg -> don't re-snap
|
||||
int noFrame; // ",noframe" in the cfg -> WS_POPUP, drag by the surface
|
||||
};
|
||||
|
||||
static GWin gWins[8];
|
||||
@@ -379,12 +380,41 @@ static void
|
||||
// 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.
|
||||
//
|
||||
// PER-LINE OPTION ",noframe" (2026-07-29): append it to a window's line and
|
||||
// that window loses its title bar and border (WS_POPUP) -- for a clean
|
||||
// multi-monitor wall where the chrome is just noise.
|
||||
//
|
||||
// Heat MFD=1920,0,657,539,noframe
|
||||
//
|
||||
// THE WORKFLOW IS DELIBERATELY TWO-STAGE, and the option is the last step:
|
||||
// 1. run with BT_GLASS_LAYOUT=save and DRAG the windows where you want them
|
||||
// (they have title bars, so this is the normal sticky-layout flow),
|
||||
// 2. quit,
|
||||
// 3. hand-edit the cfg and append ,noframe to the ones you want bare.
|
||||
// From then on those windows come up frameless AND PINNED -- with no caption
|
||||
// there is nothing to drag them by, which is the point: the arrangement is
|
||||
// finished. Re-frame a window by deleting the flag.
|
||||
//
|
||||
// The one thing it must not do is eat itself: SaveLayout rewrites the WHOLE
|
||||
// file (any later drag of a FRAMED window triggers it), so it writes the flag
|
||||
// back for every window that carries it.
|
||||
//###########################################################################
|
||||
|
||||
enum { LayoutOff = 0, LayoutLoad = 1, LayoutSave = 2 };
|
||||
|
||||
static const char *layoutFileName = "glass_layout.cfg";
|
||||
|
||||
// A framed window is a normal tool window; ",noframe" makes it a bare
|
||||
// WS_POPUP -- no caption, no border, nothing but the display and its buttons.
|
||||
static DWORD
|
||||
GlassWindowStyle(const GWin &w)
|
||||
{
|
||||
return w.noFrame
|
||||
? (DWORD)WS_POPUP
|
||||
: (DWORD)(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
|
||||
}
|
||||
|
||||
static int
|
||||
GlassLayoutMode()
|
||||
{
|
||||
@@ -424,7 +454,7 @@ static GWin *
|
||||
// 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()
|
||||
LoadLayout(int quiet)
|
||||
{
|
||||
if (GlassLayoutMode() == LayoutOff)
|
||||
return;
|
||||
@@ -432,8 +462,9 @@ static void
|
||||
FILE *f = fopen(layoutFileName, "rt");
|
||||
if (f == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "[glasswin] no " << layoutFileName
|
||||
<< " yet (using computed placement)\n" << std::flush;
|
||||
if (!quiet)
|
||||
DEBUG_STREAM << "[glasswin] no " << layoutFileName
|
||||
<< " yet (using computed placement)\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -459,17 +490,34 @@ static void
|
||||
if (sscanf(eq + 1, "%d,%d,%d,%d", &x, &y, &w, &h) < 2)
|
||||
continue;
|
||||
|
||||
// Trailing options after the numbers, comma-separated. Unknown ones
|
||||
// are ignored (so an older build reading a newer file just loses the
|
||||
// option, never the line) -- the bindings.txt grammar rule.
|
||||
int noframe = 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)
|
||||
{
|
||||
noframe = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GWin *gw = FindGWinByTitle(s);
|
||||
if (gw == NULL)
|
||||
continue;
|
||||
gw->wantX = x;
|
||||
gw->wantY = y;
|
||||
gw->noFrame = noframe;
|
||||
gw->restored = 1;
|
||||
++restored;
|
||||
}
|
||||
fclose(f);
|
||||
DEBUG_STREAM << "[glasswin] restored " << restored << " window position(s) from "
|
||||
<< layoutFileName << "\n" << std::flush;
|
||||
if (!quiet)
|
||||
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
|
||||
@@ -490,7 +538,14 @@ static void
|
||||
}
|
||||
|
||||
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",
|
||||
"# on finished-drag/exit; =load restores it). <title>=<x>,<y>,<w>,<h>\n"
|
||||
"#\n"
|
||||
"# Happy with where everything sits? Quit, then append ,noframe to any\n"
|
||||
"# line to bring that window up with no title bar or border:\n"
|
||||
"# 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",
|
||||
f);
|
||||
int wrote = 0;
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
@@ -501,9 +556,10 @@ static void
|
||||
RECT r;
|
||||
if (!GetWindowRect(gw.hwnd, &r))
|
||||
continue;
|
||||
fprintf(f, "%s=%ld,%ld,%ld,%ld\n", gw.title,
|
||||
fprintf(f, "%s=%ld,%ld,%ld,%ld%s\n", gw.title,
|
||||
(long)r.left, (long)r.top,
|
||||
(long)(r.right - r.left), (long)(r.bottom - r.top));
|
||||
(long)(r.right - r.left), (long)(r.bottom - r.top),
|
||||
gw.noFrame ? ",noframe" : ""); // keep the hand-added option
|
||||
++wrote;
|
||||
}
|
||||
fclose(f);
|
||||
@@ -793,6 +849,7 @@ static LRESULT CALLBACK
|
||||
case WM_ERASEBKGND:
|
||||
return 1; // PaintGlass fills the frame (double-buffered)
|
||||
|
||||
|
||||
case WM_TIMER:
|
||||
if (wparam == RepaintTimerId)
|
||||
{
|
||||
@@ -926,18 +983,20 @@ void
|
||||
wc.lpszClassName = L"BTGlassWin";
|
||||
RegisterClassW(&wc);
|
||||
|
||||
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
|
||||
// The ",noframe" flag is per WINDOW and arrives with the layout file, so
|
||||
// read that FIRST -- the frame size (and therefore ComputeLayout's ring
|
||||
// placement) depends on which windows carry chrome.
|
||||
LoadLayout(1); // quiet: this pass only needs the ",noframe" flags
|
||||
|
||||
// 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);
|
||||
AdjustWindowRect(&fr, GlassWindowStyle(gWins[i]), FALSE);
|
||||
gWins[i].frameW = fr.right - fr.left;
|
||||
gWins[i].frameH = fr.bottom - fr.top;
|
||||
}
|
||||
ComputeLayout();
|
||||
LoadLayout(); // BT_GLASS_LAYOUT=load/save: restore saved positions over the ring
|
||||
LoadLayout(0); // re-apply saved positions over the computed ring
|
||||
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
{
|
||||
@@ -951,7 +1010,7 @@ void
|
||||
// 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,
|
||||
L"BTGlassWin", wtitle, GlassWindowStyle(w),
|
||||
w.wantX, w.wantY, w.frameW, w.frameH,
|
||||
NULL, NULL, GetModuleHandleW(NULL), NULL);
|
||||
if (w.hwnd == NULL)
|
||||
|
||||
@@ -297,6 +297,13 @@ static const char *kEnvironIniDefault =
|
||||
"# load restore the saved layout but never change it (read-only)\n"
|
||||
"# The positions live in glass_layout.cfg beside this file -- delete it to go\n"
|
||||
"# back to the pod ring. Windows not in the file keep their computed spot.\n"
|
||||
"#\n"
|
||||
"# Once everything is where you want it, quit and append ,noframe to any line\n"
|
||||
"# in that file to bring that window up with no title bar or border -- handy\n"
|
||||
"# on a multi-monitor wall where the chrome is just noise:\n"
|
||||
"# Heat MFD=1920,0,657,539,noframe\n"
|
||||
"# Arrange FIRST: a frameless window is also pinned (no caption means nothing\n"
|
||||
"# to drag it by). Delete the flag to get the frame back.\n"
|
||||
"#BT_GLASS_LAYOUT=save\n"
|
||||
"\n"
|
||||
"# Size of the secondary displays in the cockpit surround, as a percentage of\n"
|
||||
|
||||
Reference in New Issue
Block a user