From 16ce4dfbeadbbf0282aa3e80959413fae0cdcc86 Mon Sep 17 00:00:00 2001 From: Cyd Date: Wed, 29 Jul 2026 08:28:44 -0500 Subject: [PATCH] Exploded view remembers where you drag its windows Ported from BT411's BT_GLASS_LAYOUT (29c502d). The exploded view's panes are draggable desktop windows, but the arrangement is recomputed on every launch, so dragging one somewhere useful never survived the menu-race-menu loop. RP412MFDLAYOUT persists it to mfd_layout.cfg beside bindings.txt: off / 0 / unset computed arrangement only, no file (default) load / restore restore saved positions at startup, never write save / adjust restore, then rewrite on each finished drag (WM_EXITSIZEMOVE) and on teardown One "=x,y,w,h" line per pane. Position is restored and the size read and discarded: a pane's size follows its content and its button banks, so letting an old size back in would misshape it after any geometry change - and this port has changed that geometry twice already. Load runs after the computed arrangement rather than instead of it, so a pane the file does not mention simply keeps its computed spot. Only the exploded view registers: the composited cockpit's panes are chrome-less children with nothing to drag, so they have no position worth keeping. RP412 needs no equivalent of BT411's "restored" flag. Its re-snap is LayoutCockpit on WM_SIZE, which only runs in cockpit mode, so nothing comes back later to overwrite a hand-placed window. Verified by round trip: dragged Map to 777,333 in save mode, the file took all six panes, and a fresh launch in load mode put it physically back at 777,333. The harness also resized the window while moving it, which incidentally proved the saved size really is ignored - the pane came back correctly sized from a cfg that recorded 136x39. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .gitignore | 4 + MUNGA_L4/L4MFDVIEW.cpp | 208 +++++++++++++++++++++++++++++++++++++++++ MUNGA_L4/L4MFDVIEW.h | 46 +++++++++ MUNGA_L4/L4VB16.cpp | 15 +++ pack-dist.ps1 | 12 +++ 5 files changed, 285 insertions(+) diff --git a/.gitignore b/.gitignore index d31369c..4360b5d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,10 @@ ipch/ *.log rpl4.log +# Where the exploded view's panes were last dragged to (RP412MFDLAYOUT). +# Per-machine by nature - it is screen coordinates on somebody's desk. +mfd_layout.cfg + # Build-output static libs that land in lib/ (the two committed dependency # libs, OpenAL32.lib and libsndfile-1.lib, stay tracked). /lib/Munga_L4.lib diff --git a/MUNGA_L4/L4MFDVIEW.cpp b/MUNGA_L4/L4MFDVIEW.cpp index a762ef7..03bae49 100644 --- a/MUNGA_L4/L4MFDVIEW.cpp +++ b/MUNGA_L4/L4MFDVIEW.cpp @@ -10,6 +10,52 @@ namespace const int buttonGap = 4; + //--------------------------------------------------------------- + // Sticky placement for the exploded view. See L4MFDVIEW.h; ported + // from BT411's BT_GLASS_LAYOUT. + //--------------------------------------------------------------- + enum { LayoutOff = 0, LayoutLoad, LayoutSave }; + + const char layoutFileName[] = "mfd_layout.cfg"; + + // the panes that own a desktop window, so Save can walk them + enum { maxLayoutPanes = 8 }; + MFDSplitView *layoutPanes[maxLayoutPanes]; + int layoutPaneCount = 0; + + int LayoutMode() + { + static int mode = -1; + if (mode < 0) + { + const char *setting = getenv("RP412MFDLAYOUT"); + if (setting == NULL || setting[0] == '\0') + { + mode = LayoutOff; + } + else if (!stricmp(setting, "off") || !stricmp(setting, "0")) + { + mode = LayoutOff; + } + else if (!stricmp(setting, "load") || !stricmp(setting, "restore")) + { + mode = LayoutLoad; + } + else // save / adjust / on / 1 + { + mode = LayoutSave; + } + + if (mode != LayoutOff) + { + DEBUG_STREAM << "MFDLayout: RP412MFDLAYOUT=" + << ((mode == LayoutSave) ? "save" : "load") + << " (" << layoutFileName << ")\n" << std::flush; + } + } + return mode; + } + //--------------------------------------------------------------- // Button banks run UNDER the glass: a button reaches this far in // from the display edge, but only the indicator strip clears that @@ -108,6 +154,15 @@ namespace } break; + case WM_EXITSIZEMOVE: + // + // A drag just finished. Write the whole arrangement now rather + // than trusting teardown, so a hard kill still leaves the + // layout that was on screen. No-op unless the mode is save. + // + MFDSplitView_SaveLayout(); + return 0; + case WM_CLOSE: // Part of the cockpit; just hide it. ShowWindow(hwnd, SW_HIDE); @@ -148,6 +203,8 @@ MFDSplitView::MFDSplitView( sourceHeight = source_height; buttonCount = 0; pressedIndex = -1; + paneTitle = title; + ownWindow = (parent == NULL) ? True : False; pixels = new unsigned long[source_width * source_height]; memset(pixels, 0, source_width * source_height * sizeof(unsigned long)); @@ -230,6 +287,13 @@ MFDSplitView::MFDSplitView( { SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this); ShowWindow((HWND) window, SW_SHOWNOACTIVATE); + + // only the exploded view's own windows can be dragged, so only + // those have a position worth remembering + if (ownWindow && layoutPaneCount < maxLayoutPanes) + { + layoutPanes[layoutPaneCount++] = this; + } } else { @@ -241,6 +305,17 @@ MFDSplitView::~MFDSplitView() { Check_Pointer(this); + // out of the layout registry before the window goes + for (int i = 0; i < layoutPaneCount; ++i) + { + if (layoutPanes[i] == this) + { + layoutPanes[i] = layoutPanes[--layoutPaneCount]; + layoutPanes[layoutPaneCount] = NULL; + break; + } + } + if (window != NULL) { SetWindowLongPtrA((HWND) window, GWLP_USERDATA, 0); @@ -259,6 +334,139 @@ Logical return pixels != NULL; } +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Sticky placement: restore saved positions over the computed ones. +// +// Called after the exploded view has built and placed every pane, so a +// pane the file does not mention simply keeps where the layout put it. +// Position only - the saved size is read and discarded, because a pane's +// size comes from its content and its button banks, and letting an old +// w,h back in would misshape it after any geometry change. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void + MFDSplitView_LoadLayout() +{ + if (LayoutMode() == LayoutOff || layoutPaneCount == 0) + { + return; + } + + FILE *file = fopen(layoutFileName, "rt"); + if (file == NULL) + { + DEBUG_STREAM << "MFDLayout: no " << layoutFileName + << " yet (using the computed arrangement)\n" << std::flush; + return; + } + + int restored = 0; + char line[256]; + while (fgets(line, sizeof(line), file) != NULL) + { + char *cursor = line; + while (*cursor == ' ' || *cursor == '\t') + { + ++cursor; + } + if (*cursor == '#' || *cursor == '\r' || *cursor == '\n' || *cursor == '\0') + { + continue; + } + + // titles carry no '=', so the first one splits the line + char *equals = strchr(cursor, '='); + if (equals == NULL) + { + continue; + } + *equals = '\0'; + char *end = equals; + while (end > cursor && (end[-1] == ' ' || end[-1] == '\t')) + { + --end; + } + *end = '\0'; + + int x = 0, y = 0, w = 0, h = 0; + if (sscanf(equals + 1, "%d,%d,%d,%d", &x, &y, &w, &h) < 2) + { + continue; + } + + for (int i = 0; i < layoutPaneCount; ++i) + { + MFDSplitView *pane = layoutPanes[i]; + if (pane != NULL && pane->Title() != NULL && + !strcmp(pane->Title(), cursor)) + { + pane->SetPosition(x, y); + ++restored; + break; + } + } + } + fclose(file); + + DEBUG_STREAM << "MFDLayout: restored " << restored << " pane position(s) from " + << layoutFileName << "\n" << std::flush; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Write where every open pane currently is. The whole file each time - it +// is a handful of lines, and a rewrite leaves nothing half-written for a +// hard kill to catch. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void + MFDSplitView_SaveLayout() +{ + if (LayoutMode() != LayoutSave || layoutPaneCount == 0) + { + return; + } + + FILE *file = fopen(layoutFileName, "wt"); + if (file == NULL) + { + DEBUG_STREAM << "MFDLayout: could not write " << layoutFileName + << "\n" << std::flush; + return; + } + + fputs("# RP412 exploded-view pane placement. RP412MFDLAYOUT=save writes\n" + "# this on each finished drag and on exit; =load restores it.\n" + "# <title>=<x>,<y>,<w>,<h> - the size is written for reference and\n" + "# ignored on load.\n", file); + + int wrote = 0; + for (int i = 0; i < layoutPaneCount; ++i) + { + MFDSplitView *pane = layoutPanes[i]; + if (pane == NULL || pane->Title() == NULL) + { + continue; + } + HWND pane_window = (HWND) pane->Window(); + if (pane_window == NULL || !IsWindow(pane_window)) + { + continue; + } + RECT bounds; + if (!GetWindowRect(pane_window, &bounds)) + { + continue; + } + fprintf(file, "%s=%ld,%ld,%ld,%ld\n", pane->Title(), + (long) bounds.left, (long) bounds.top, + (long) (bounds.right - bounds.left), + (long) (bounds.bottom - bounds.top)); + ++wrote; + } + fclose(file); + + DEBUG_STREAM << "MFDLayout: saved " << wrote << " pane position(s) to " + << layoutFileName << "\n" << std::flush; +} + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Compute the display rectangle and the button rectangles, growing the // client area to make room for the strips. diff --git a/MUNGA_L4/L4MFDVIEW.h b/MUNGA_L4/L4MFDVIEW.h index 6328e9f..a349b4f 100644 --- a/MUNGA_L4/L4MFDVIEW.h +++ b/MUNGA_L4/L4MFDVIEW.h @@ -77,6 +77,14 @@ public: void Hide(); + // caption and HWND, for the sticky-placement file + const char * + Title() const + { return paneTitle; } + void * + Window() + { return window; } + ~MFDSplitView(); Logical @@ -147,4 +155,42 @@ protected: int buttonAnchorA, buttonAnchorB; + + // the window caption, and the key this pane is saved under + const char + *paneTitle; + // True for a pane of its own on the desktop (the exploded view); the + // composited cockpit's panes are chrome-less children and cannot be + // dragged, so there is nothing to remember for them. + Logical + ownWindow; }; + +//######################################################################## +// Sticky window placement for the exploded view (L4MFDSPLIT=2). +// +// Those panes are draggable desktop windows, but the arrangement is +// recomputed on every launch, so dragging one somewhere useful never +// survived the menu-race-menu loop. RP412MFDLAYOUT persists it to +// mfd_layout.cfg beside bindings.txt: +// +// off / 0 / unset computed arrangement only, no file (default) +// load / restore restore saved positions at startup, never write +// save / adjust restore, then rewrite on each finished drag and +// on teardown - the round trip +// +// One "<title>=x,y,w,h" line per pane. Position is restored and size +// ignored: a pane's size follows its content and its button banks, so an +// old w,h from a different build must not distort it. +// +// Ported from BT411's BT_GLASS_LAYOUT. +//######################################################################## + +// Apply saved positions over the computed ones. Call after the panes are +// built and placed. +void + MFDSplitView_LoadLayout(); + +// Write every open pane's position. No-op unless the mode is save. +void + MFDSplitView_SaveLayout(); diff --git a/MUNGA_L4/L4VB16.cpp b/MUNGA_L4/L4VB16.cpp index e3934fa..6d75ef6 100644 --- a/MUNGA_L4/L4VB16.cpp +++ b/MUNGA_L4/L4VB16.cpp @@ -4457,6 +4457,13 @@ SVGA16::SVGA16( splitView[SplitMFDLowerRight]->SetPosition( work.left + right_x, work.top + bottom_y); splitView[SplitMap]->SetPosition(work.left + map_x, work.top + map_y); + + // + // ...and then let RP412MFDLAYOUT put back anywhere they were + // dragged to last time. Panes the file does not mention keep the + // arrangement just computed above. + // + MFDSplitView_LoadLayout(); } else if (splitViews) { @@ -4863,6 +4870,14 @@ SVGA16::~SVGA16() //--------------------------------------------------------- //SVGASetSplitterClock(False); + // + // Backstop for the sticky exploded-view placement: every finished drag + // has already been written, but a pane moved and then closed straight + // away would otherwise be missed. Must run before the panes go - it + // reads their live window rects. + // + MFDSplitView_SaveLayout(); + for (int view = 0; view < SplitViewCount; ++view) { delete splitView[view]; diff --git a/pack-dist.ps1 b/pack-dist.ps1 index adf987a..0895180 100644 --- a/pack-dist.ps1 +++ b/pack-dist.ps1 @@ -166,6 +166,18 @@ L4PLASMA=SCREEN # 480x640 - decoded exactly as the pod's VDB split them, no downscale). L4MFDSPLIT=1 +# In the exploded view (L4MFDSPLIT=2) each display is a window you can +# drag, but the arrangement is recomputed every launch, so moving one +# somewhere useful never survived the menu-race-menu loop. This remembers +# it, in mfd_layout.cfg beside this file: +# off / 0 / unset computed arrangement only, no file (default) +# load put the panes back where they were saved +# save the same, and re-save on every finished drag +# Only positions are remembered - a pane's size follows its content and +# its button banks, so an old size is never restored over it. Drag the +# displays where you want them once with save, then leave it on load. +#RP412MFDLAYOUT=off + # Size of the six secondary displays in the glass cockpit, as a # percentage of their pod size. The pod bolted them down at one size; # on a big panel there is room to trade viewscreen for instrument, so