diff --git a/.gitignore b/.gitignore
index 4360b5d..fff37de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,8 +40,9 @@ 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.
+# Where the game window and 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
diff --git a/MUNGA_L4/L4MFDVIEW.cpp b/MUNGA_L4/L4MFDVIEW.cpp
index 03bae49..27ac97a 100644
--- a/MUNGA_L4/L4MFDVIEW.cpp
+++ b/MUNGA_L4/L4MFDVIEW.cpp
@@ -18,10 +18,16 @@ namespace
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;
+ // every window taking part, so Save can walk them
+ enum { maxLayoutWindows = 12 };
+ struct LayoutWindow
+ {
+ void *window;
+ const char *title;
+ Logical withSize;
+ };
+ LayoutWindow layoutWindows[maxLayoutWindows];
+ int layoutWindowCount = 0;
int LayoutMode()
{
@@ -160,7 +166,7 @@ namespace
// than trusting teardown, so a hard kill still leaves the
// layout that was on screen. No-op unless the mode is save.
//
- MFDSplitView_SaveLayout();
+ RPWindowLayout_Save();
return 0;
case WM_CLOSE:
@@ -289,10 +295,11 @@ MFDSplitView::MFDSplitView(
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)
+ // those have a placement worth remembering. Position only - see
+ // RPWindowLayout_Register.
+ if (ownWindow)
{
- layoutPanes[layoutPaneCount++] = this;
+ RPWindowLayout_Register(window, paneTitle, False);
}
}
else
@@ -306,15 +313,7 @@ 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;
- }
- }
+ RPWindowLayout_Forget(window);
if (window != NULL)
{
@@ -335,18 +334,61 @@ Logical
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// 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.
+// Join the saved layout. Registering twice just refreshes the entry, so
+// a window re-registering after being rebuilt is harmless.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
- MFDSplitView_LoadLayout()
+ RPWindowLayout_Register(void *window, const char *title, Logical with_size)
{
- if (LayoutMode() == LayoutOff || layoutPaneCount == 0)
+ if (window == NULL || title == NULL)
+ {
+ return;
+ }
+ for (int i = 0; i < layoutWindowCount; ++i)
+ {
+ if (layoutWindows[i].window == window)
+ {
+ layoutWindows[i].title = title;
+ layoutWindows[i].withSize = with_size;
+ return;
+ }
+ }
+ if (layoutWindowCount >= maxLayoutWindows)
+ {
+ return;
+ }
+ layoutWindows[layoutWindowCount].window = window;
+ layoutWindows[layoutWindowCount].title = title;
+ layoutWindows[layoutWindowCount].withSize = with_size;
+ ++layoutWindowCount;
+}
+
+void
+ RPWindowLayout_Forget(void *window)
+{
+ for (int i = 0; i < layoutWindowCount; ++i)
+ {
+ if (layoutWindows[i].window == window)
+ {
+ layoutWindows[i] = layoutWindows[--layoutWindowCount];
+ layoutWindows[layoutWindowCount].window = NULL;
+ layoutWindows[layoutWindowCount].title = NULL;
+ return;
+ }
+ }
+}
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Sticky placement: restore the saved placement over the computed one.
+//
+// Called once everything has been built and placed, so a window the file
+// does not mention simply keeps where it was put. Size comes back only
+// for windows registered with it - see the header.
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+void
+ RPWindowLayout_Load()
+{
+ if (LayoutMode() == LayoutOff || layoutWindowCount == 0)
{
return;
}
@@ -393,33 +435,75 @@ void
continue;
}
- for (int i = 0; i < layoutPaneCount; ++i)
+ for (int i = 0; i < layoutWindowCount; ++i)
{
- MFDSplitView *pane = layoutPanes[i];
- if (pane != NULL && pane->Title() != NULL &&
- !strcmp(pane->Title(), cursor))
+ LayoutWindow &entry = layoutWindows[i];
+ if (entry.title == NULL || strcmp(entry.title, cursor) != 0)
+ {
+ continue;
+ }
+ HWND target = (HWND) entry.window;
+ if (target == NULL || !IsWindow(target))
{
- pane->SetPosition(x, y);
- ++restored;
break;
}
+
+ // a saved size of nothing is a corrupt line, not an instruction
+ Logical size_it = (entry.withSize && w > 0 && h > 0) ? True : False;
+
+ //
+ // Saved coordinates belong to whatever monitors were plugged
+ // in that day. If the placement lands on none of today's, drop
+ // it and keep the computed one - restoring the game window
+ // somewhere invisible would leave nothing to drag back.
+ //
+ RECT landing;
+ landing.left = x;
+ landing.top = y;
+ if (size_it)
+ {
+ landing.right = x + w;
+ landing.bottom = y + h;
+ }
+ else
+ {
+ RECT current;
+ if (!GetWindowRect(target, ¤t))
+ {
+ break;
+ }
+ landing.right = x + (current.right - current.left);
+ landing.bottom = y + (current.bottom - current.top);
+ }
+ if (MonitorFromRect(&landing, MONITOR_DEFAULTTONULL) == NULL)
+ {
+ DEBUG_STREAM << "MFDLayout: " << cursor << " was saved at "
+ << x << "," << y << " - off every monitor now, ignoring\n"
+ << std::flush;
+ break;
+ }
+
+ SetWindowPos(target, NULL, x, y, w, h,
+ SWP_NOZORDER | SWP_NOACTIVATE | (size_it ? 0 : SWP_NOSIZE));
+ ++restored;
+ break;
}
}
fclose(file);
- DEBUG_STREAM << "MFDLayout: restored " << restored << " pane position(s) from "
+ DEBUG_STREAM << "MFDLayout: restored " << restored << " window placement(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.
+// Write where every registered window 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()
+ RPWindowLayout_Save()
{
- if (LayoutMode() != LayoutSave || layoutPaneCount == 0)
+ if (LayoutMode() != LayoutSave || layoutWindowCount == 0)
{
return;
}
@@ -432,30 +516,41 @@ void
return;
}
- fputs("# RP412 exploded-view pane placement. RP412MFDLAYOUT=save writes\n"
- "# this on each finished drag and on exit; =load restores it.\n"
- "#
=,,, - the size is written for reference and\n"
- "# ignored on load.\n", file);
+ fputs("# RP412 window placement. RP412MFDLAYOUT=save writes this on each\n"
+ "# finished drag and on exit; =load restores it.\n"
+ "# =,,,. The game window gets its size back too;\n"
+ "# for the display panes the size is reference only, since theirs\n"
+ "# follows their content.\n", file);
int wrote = 0;
- for (int i = 0; i < layoutPaneCount; ++i)
+ for (int i = 0; i < layoutWindowCount; ++i)
{
- MFDSplitView *pane = layoutPanes[i];
- if (pane == NULL || pane->Title() == NULL)
+ LayoutWindow &entry = layoutWindows[i];
+ if (entry.title == NULL)
{
continue;
}
- HWND pane_window = (HWND) pane->Window();
- if (pane_window == NULL || !IsWindow(pane_window))
+ HWND entry_window = (HWND) entry.window;
+ if (entry_window == NULL || !IsWindow(entry_window))
{
continue;
}
- RECT bounds;
- if (!GetWindowRect(pane_window, &bounds))
+ //
+ // rcNormalPosition rather than GetWindowRect: a minimised window
+ // reports a nonsense rect and a maximised one reports the screen,
+ // and neither is what to come back to. This is the restored
+ // placement whatever state the window is in, so quitting from
+ // maximised still records where the window will reappear.
+ //
+ WINDOWPLACEMENT placement;
+ memset(&placement, 0, sizeof(placement));
+ placement.length = sizeof(placement);
+ if (!GetWindowPlacement(entry_window, &placement))
{
continue;
}
- fprintf(file, "%s=%ld,%ld,%ld,%ld\n", pane->Title(),
+ RECT bounds = placement.rcNormalPosition;
+ fprintf(file, "%s=%ld,%ld,%ld,%ld\n", entry.title,
(long) bounds.left, (long) bounds.top,
(long) (bounds.right - bounds.left),
(long) (bounds.bottom - bounds.top));
@@ -463,7 +558,7 @@ void
}
fclose(file);
- DEBUG_STREAM << "MFDLayout: saved " << wrote << " pane position(s) to "
+ DEBUG_STREAM << "MFDLayout: saved " << wrote << " window placement(s) to "
<< layoutFileName << "\n" << std::flush;
}
diff --git a/MUNGA_L4/L4MFDVIEW.h b/MUNGA_L4/L4MFDVIEW.h
index a349b4f..f605f26 100644
--- a/MUNGA_L4/L4MFDVIEW.h
+++ b/MUNGA_L4/L4MFDVIEW.h
@@ -167,30 +167,58 @@ protected:
};
//########################################################################
-// Sticky window placement for the exploded view (L4MFDSPLIT=2).
+// Sticky window placement.
//
-// 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:
+// The game window and, in the exploded view, each display pane are all
+// draggable, but their placement is recomputed on every launch - so
+// putting a window somewhere useful never survived the menu-race-menu
+// loop. RP412MFDLAYOUT remembers it, in mfd_layout.cfg beside
+// bindings.txt:
//
-// off / 0 / unset computed arrangement only, no file (default)
-// load / restore restore saved positions at startup, never write
+// off / 0 / unset computed placement only, no file (default)
+// load / restore restore saved placement at startup, never write
// save / adjust restore, then rewrite on each finished drag and
// on teardown - the round trip
//
-// One "=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.
+// One "=x,y,w,h" line per window.
+//
+// Whether the size comes back depends on the window, which is why
+// Register takes it as a flag:
+//
+// display panes position only. A pane's size follows its content and
+// its button banks, so an old size from a different
+// build must not distort it.
+// the game window position, and size in the cockpit view. Nothing
+// derives that size - the cockpit fits itself to
+// whatever client area it is given - so a window sized
+// to suit a monitor should come back that way, and
+// half-restoring it would be the strange behaviour. In
+// the exploded view its size IS the render resolution
+// -res asked for, so there only the position returns.
+//
+// A placement that lands on none of the monitors currently plugged in is
+// ignored rather than applied - restoring the game window off screen
+// would leave nothing to drag back.
+//
+// Lives here because the display panes were the first to need it; the
+// game window joined later rather than grow a second copy of the same
+// file format.
//
// Ported from BT411's BT_GLASS_LAYOUT.
//########################################################################
-// Apply saved positions over the computed ones. Call after the panes are
-// built and placed.
+// Take part in the saved layout. title is the key in the file and must
+// outlive the window (the callers pass string literals).
void
- MFDSplitView_LoadLayout();
+ RPWindowLayout_Register(void *window, const char *title, Logical with_size);
+void
+ RPWindowLayout_Forget(void *window);
-// Write every open pane's position. No-op unless the mode is save.
+// Apply the saved placement over the computed one. Call once everything
+// has been built and placed.
void
- MFDSplitView_SaveLayout();
+ RPWindowLayout_Load();
+
+// Write every registered window's placement. No-op unless mode is save.
+void
+ RPWindowLayout_Save();
diff --git a/MUNGA_L4/L4VB16.cpp b/MUNGA_L4/L4VB16.cpp
index 6d75ef6..d34d1a8 100644
--- a/MUNGA_L4/L4VB16.cpp
+++ b/MUNGA_L4/L4VB16.cpp
@@ -3839,6 +3839,15 @@ static LRESULT CALLBACK
cockpit->LayoutCockpit(LOWORD(lParam), HIWORD(lParam));
}
}
+ else if (message == WM_EXITSIZEMOVE)
+ {
+ //
+ // The shell was just dragged or resized. Write the arrangement now
+ // rather than trusting teardown, so a hard kill still leaves what
+ // was on screen. No-op unless RP412MFDLAYOUT is save.
+ //
+ RPWindowLayout_Save();
+ }
return CallWindowProcA(gCockpitBaseProc, hwnd, message, wParam, lParam);
}
@@ -4459,11 +4468,24 @@ SVGA16::SVGA16(
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
+ // The game window joins them, position only: here its size is the
+ // render resolution -res asked for, so restoring an old one would
+ // leave a stretched back buffer in a window the wrong size.
+ //
+ {
+ HWND shell = ApplicationManager::GetCurrentManager()->GetHWnd();
+ if (shell != NULL)
+ {
+ RPWindowLayout_Register(shell, "RPL4", False);
+ }
+ }
+
+ //
+ // ...and then let RP412MFDLAYOUT put everything back where it was
+ // dragged to last time. Windows the file does not mention keep the
// arrangement just computed above.
//
- MFDSplitView_LoadLayout();
+ RPWindowLayout_Load();
}
else if (splitViews)
{
@@ -4675,6 +4697,23 @@ SVGA16::SVGA16(
// catch maximise / restore / drag-resize and re-fit
gCockpitBaseProc = (WNDPROC) SetWindowLongPtrA(
cockpit, GWLP_WNDPROC, (LONG_PTR) CockpitShellProc);
+
+ //
+ // Sticky placement for the shell. Position AND size: nothing
+ // derives that size here - -res only decides how sharp the
+ // scene is - so a window sized to suit a monitor should come
+ // back that way. Registered after the subclass on purpose, so
+ // the restore's WM_SIZE runs LayoutCockpit again and the
+ // canvas re-fits the restored client area.
+ //
+ // -fit sits it borderless over the whole monitor, so there is
+ // no placement of the player's to remember.
+ //
+ if (!fit_display)
+ {
+ RPWindowLayout_Register(cockpit, "RPL4", True);
+ RPWindowLayout_Load();
+ }
}
else
{
@@ -4871,12 +4910,12 @@ 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.
+ // Backstop for the sticky placement: every finished drag has already
+ // been written, but a window moved and then closed straight away would
+ // otherwise be missed. Must run before the panes go - it reads their
+ // live window rects, and the game window's.
//
- MFDSplitView_SaveLayout();
+ RPWindowLayout_Save();
for (int view = 0; view < SplitViewCount; ++view)
{
diff --git a/pack-dist.ps1 b/pack-dist.ps1
index 0895180..fb46cbc 100644
--- a/pack-dist.ps1
+++ b/pack-dist.ps1
@@ -166,16 +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
+# The game window - and in the exploded view (L4MFDSPLIT=2) each display
+# window - is placed fresh every launch, so moving one somewhere useful
+# never survived the menu-race-menu loop. This remembers where you put
+# them, in mfd_layout.cfg beside this file:
+# off / 0 / unset computed placement only, no file (default)
+# load put the windows 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.
+# The game window gets its size back too, so you can size the cockpit to
+# suit your monitor once and keep it. The display windows get position
+# only: their size follows their content and their button banks, so an
+# old one is never restored over them. Arrange everything once with
+# save, then leave it on load.
#RP412MFDLAYOUT=off
# Size of the six secondary displays in the glass cockpit, as a