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" - "# =<x>,<y>,<w>,<h> - 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" + "# <title>=<x>,<y>,<w>,<h>. 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 "<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. +// One "<title>=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