diff --git a/MUNGA_L4/L4MFDVIEW.cpp b/MUNGA_L4/L4MFDVIEW.cpp index 9f9a6aa..c4fe40b 100644 --- a/MUNGA_L4/L4MFDVIEW.cpp +++ b/MUNGA_L4/L4MFDVIEW.cpp @@ -145,6 +145,10 @@ MFDSplitView::MFDSplitView( header->biCompression = BI_RGB; blitHeader = header; + buttonStyle = button_style; + buttonAnchorA = anchorA; + buttonAnchorB = anchorB; + int client_width = display_width; int client_height = display_height; LayoutButtons(button_style, anchorA, anchorB, @@ -472,6 +476,34 @@ void } } +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Re-scale to a new glass size: the pixel buffer keeps its native +// source resolution, so only the destination rectangle, the button +// geometry and the window size change. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void + MFDSplitView::Resize(int display_width, int display_height) +{ + Check_Pointer(this); + + buttonCount = 0; + pressedIndex = -1; + + int client_width = display_width; + int client_height = display_height; + LayoutButtons(buttonStyle, buttonAnchorA, buttonAnchorB, + display_width, display_height, &client_width, &client_height); + clientWidth = client_width; + clientHeight = client_height; + + if (window != NULL) + { + SetWindowPos((HWND) window, NULL, 0, 0, + client_width, client_height, + SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + } +} + void MFDSplitView::Repaint() { diff --git a/MUNGA_L4/L4MFDVIEW.h b/MUNGA_L4/L4MFDVIEW.h index 86aa8a2..9948a3e 100644 --- a/MUNGA_L4/L4MFDVIEW.h +++ b/MUNGA_L4/L4MFDVIEW.h @@ -65,6 +65,12 @@ public: void SetPosition(int x, int y); + // Re-scale the glass (cockpit resized): the source buffer is + // unchanged, only the destination size and the button geometry + // derived from it. + void + Resize(int display_width, int display_height); + ~MFDSplitView(); Logical @@ -128,4 +134,11 @@ protected: buttonCount; int pressedIndex; + + // kept so the bank can be rebuilt when the cockpit re-scales + ButtonStyle + buttonStyle; + int + buttonAnchorA, + buttonAnchorB; }; diff --git a/MUNGA_L4/L4VB16.cpp b/MUNGA_L4/L4VB16.cpp index 0114c9b..7113ce6 100644 --- a/MUNGA_L4/L4VB16.cpp +++ b/MUNGA_L4/L4VB16.cpp @@ -3821,6 +3821,146 @@ static LRESULT CALLBACK return CallWindowProcA(gViewscreenBaseProc, hwnd, message, wParam, lParam); } +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// The cockpit shell: re-fit the canvas whenever the window changes size +// (maximise, restore, drag). The game window's own proc handles the +// rest, so chain to it. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +static WNDPROC gCockpitBaseProc = NULL; + +static LRESULT CALLBACK + CockpitShellProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message == WM_SIZE && wParam != SIZE_MINIMIZED) + { + SVGA16 *cockpit = SVGA16::GetCockpit(); + if (cockpit != NULL) + { + cockpit->LayoutCockpit(LOWORD(lParam), HIWORD(lParam)); + } + } + return CallWindowProcA(gCockpitBaseProc, hwnd, message, wParam, lParam); +} + +SVGA16 *SVGA16::activeCockpit = NULL; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Fit the 1920x1080 cockpit canvas into the given client area: one +// uniform scale (so the canvas never stretches - a wider desktop just +// letterboxes), centred, with every pane re-scaled and re-placed. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void + SVGA16::LayoutCockpit(int client_w, int client_h) +{ + Check_Pointer(this); + + if (!splitViews || client_w <= 0 || client_h <= 0) + { + return; + } + + const int canvas_w = 1920; + const int canvas_h = 1080; + + int fit_w = (client_w * 100) / canvas_w; + int fit_h = (client_h * 100) / canvas_h; + int scale = (fit_w < fit_h) ? fit_w : fit_h; + if (scale < 25) scale = 25; + + int view_w = (canvas_w * scale) / 100; + int view_h = (canvas_h * scale) / 100; + + // centre the canvas: the leftover is black on the long axis + int origin_x = (client_w - view_w) / 2; + int origin_y = (client_h - view_h) / 2; + + //--------------------------------------------------------------- + // The viewscreen fills the canvas; the 3D presents into it and + // D3D stretches the back buffer to fit, so -res only decides how + // sharp the scene is, not how big the cockpit is. + //--------------------------------------------------------------- + if (cockpitViewscreen != NULL) + { + SetWindowPos(cockpitViewscreen, NULL, + origin_x, origin_y, view_w, view_h, + SWP_NOZORDER | SWP_NOACTIVATE); + } + + int top_glass_w = (320 * scale) / 100; + int top_glass_h = (240 * scale) / 100; + // the radar reads best big: 1.35x the compact glass + int map_glass_w = (324 * scale) / 100; + int map_glass_h = (432 * scale) / 100; + + for (int view = 0; view < SplitViewCount; ++view) + { + if (splitView[view] == NULL) + { + continue; + } + if (view == SplitMap) + { + splitView[view]->Resize(map_glass_w, map_glass_h); + } + else + { + splitView[view]->Resize(top_glass_w, top_glass_h); + } + } + + //--------------------------------------------------------------- + // Top row edge to edge across the canvas, bottom cluster on the + // bottom edge - all relative to the centred canvas origin. + //--------------------------------------------------------------- + if (splitView[SplitMFDUpperLeft] != NULL) + { + splitView[SplitMFDUpperLeft]->SetPosition(origin_x, origin_y); + } + if (splitView[SplitMFDUpperCenter] != NULL) + { + splitView[SplitMFDUpperCenter]->SetPosition( + origin_x + (view_w - splitView[SplitMFDUpperCenter]->ClientWidth()) / 2, + origin_y); + } + if (splitView[SplitMFDUpperRight] != NULL) + { + splitView[SplitMFDUpperRight]->SetPosition( + origin_x + view_w - splitView[SplitMFDUpperRight]->ClientWidth(), + origin_y); + } + if (splitView[SplitMFDLowerLeft] != NULL) + { + splitView[SplitMFDLowerLeft]->SetPosition( + origin_x, + origin_y + view_h - splitView[SplitMFDLowerLeft]->ClientHeight()); + } + if (splitView[SplitMFDLowerRight] != NULL) + { + splitView[SplitMFDLowerRight]->SetPosition( + origin_x + view_w - splitView[SplitMFDLowerRight]->ClientWidth(), + origin_y + view_h - splitView[SplitMFDLowerRight]->ClientHeight()); + } + if (splitView[SplitMap] != NULL) + { + splitView[SplitMap]->SetPosition( + origin_x + (view_w - splitView[SplitMap]->ClientWidth()) / 2, + origin_y + view_h - splitView[SplitMap]->ClientHeight()); + } + + // The panes go OVER the main display (pod bezel occlusion): pin + // the viewscreen to the bottom of the sibling z-order so the 3D + // present clips around every pane. + if (cockpitViewscreen != NULL) + { + SetWindowPos(cockpitViewscreen, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + } + + DEBUG_STREAM << "SVGA16: cockpit fitted " << view_w << "x" << view_h + << " at " << scale << "% in a " << client_w << "x" << client_h + << " client\n" << std::flush; +} + SVGA16::SVGA16( int mode, int init_width, @@ -3949,18 +4089,21 @@ SVGA16::SVGA16( int avail_w = (work.right - work.left) - chrome_w; int avail_h = (work.bottom - work.top) - chrome_h; - int scale_num = 100; - if (avail_w < canvas_w || avail_h < canvas_h) - { - int fit_w = (avail_w * 100) / canvas_w; - int fit_h = (avail_h * 100) / canvas_h; - scale_num = (fit_w < fit_h) ? fit_w : fit_h; - if (scale_num < 25) scale_num = 25; + //----------------------------------------------------------- + // One uniform scale, up or down: the canvas keeps its 16:9 + // shape whatever the monitor is, so a wider-than-16:9 desktop + // letterboxes instead of stretching. Scaling UP is free + // quality on the MFDs - their glass is a downscale of a + // native 640x480 channel until about 200%. + //----------------------------------------------------------- + int fit_w = (avail_w * 100) / canvas_w; + int fit_h = (avail_h * 100) / canvas_h; + int scale_num = (fit_w < fit_h) ? fit_w : fit_h; + if (scale_num < 25) scale_num = 25; - DEBUG_STREAM << "SVGA16: cockpit canvas scaled to " << scale_num - << "% for the " << (work.right - work.left) << "x" - << (work.bottom - work.top) << " work area\n" << std::flush; - } + DEBUG_STREAM << "SVGA16: cockpit canvas at " << scale_num + << "% for the " << (work.right - work.left) << "x" + << (work.bottom - work.top) << " work area\n" << std::flush; #define COCKPIT_CANVAS(v) (((v) * scale_num) / 100) @@ -4055,58 +4198,30 @@ SVGA16::SVGA16( MFDSplitView::SideColumns, 0x10, 0x18, cockpit); //--------------------------------------------------------------- - // Place the panes from their measured sizes: top row edge to - // edge across the canvas, bottom cluster on the bottom edge. + // Everything above built the panes at a starting size; the + // layout itself lives in LayoutCockpit so a resized window can + // re-run it. Track the instance for the WM_SIZE hook below. //--------------------------------------------------------------- - if (splitView[SplitMFDUpperLeft] != NULL) - { - splitView[SplitMFDUpperLeft]->SetPosition(0, 0); - } - if (splitView[SplitMFDUpperCenter] != NULL) - { - splitView[SplitMFDUpperCenter]->SetPosition( - (client_w - splitView[SplitMFDUpperCenter]->ClientWidth()) / 2, 0); - } - if (splitView[SplitMFDUpperRight] != NULL) - { - splitView[SplitMFDUpperRight]->SetPosition( - client_w - splitView[SplitMFDUpperRight]->ClientWidth(), 0); - } - if (splitView[SplitMFDLowerLeft] != NULL) - { - splitView[SplitMFDLowerLeft]->SetPosition( - 0, client_h - splitView[SplitMFDLowerLeft]->ClientHeight()); - } - if (splitView[SplitMFDLowerRight] != NULL) - { - splitView[SplitMFDLowerRight]->SetPosition( - client_w - splitView[SplitMFDLowerRight]->ClientWidth(), - client_h - splitView[SplitMFDLowerRight]->ClientHeight()); - } - if (splitView[SplitMap] != NULL) - { - splitView[SplitMap]->SetPosition( - (client_w - splitView[SplitMap]->ClientWidth()) / 2, - client_h - splitView[SplitMap]->ClientHeight()); - } + activeCockpit = this; - // The panes go OVER the main display (pod bezel occlusion): pin - // the viewscreen to the bottom of the sibling z-order so the 3D - // present clips around every pane. - if (cockpitViewscreen != NULL) + if (cockpit != NULL) { - SetWindowPos(cockpitViewscreen, HWND_BOTTOM, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + RECT inner; + GetClientRect(cockpit, &inner); + LayoutCockpit(inner.right, inner.bottom); + + // catch maximise / restore / drag-resize and re-fit + gCockpitBaseProc = (WNDPROC) SetWindowLongPtrA( + cockpit, GWLP_WNDPROC, (LONG_PTR) CockpitShellProc); + } + else + { + LayoutCockpit(client_w, client_h); } // the plasma glass sits out for now PlasmaScreen::Hide(); - DEBUG_STREAM << "SVGA16: single-window cockpit assembled (" - << client_w << "x" << client_h << " canvas at " << scale_num - << "%, viewscreen " << view_w << "x" << view_h - << " centered)\n" << std::flush; - #undef COCKPIT_CANVAS } //STUBBED: VIDEO RB 1/15/07 diff --git a/MUNGA_L4/L4VB16.h b/MUNGA_L4/L4VB16.h index f2d0a49..5c79c90 100644 --- a/MUNGA_L4/L4VB16.h +++ b/MUNGA_L4/L4VB16.h @@ -315,12 +315,30 @@ private: int shift ); +public: + //------------------------------------------------------------------ + // Fit the cockpit into a client area: one uniform scale keeps the + // 1920x1080 canvas at its own aspect (so an ultrawide letterboxes + // rather than stretching), centred in whatever space it is given. + // Re-run whenever the cockpit window is resized. + //------------------------------------------------------------------ + void + LayoutCockpit(int client_w, int client_h); + + static SVGA16 * + GetCockpit() + { return activeCockpit; } + +protected: Logical splitViews; MFDSplitView *splitView[SplitViewCount]; HWND cockpitViewscreen; // child pane the 3D scene presents into + + static SVGA16 + *activeCockpit; // the instance owning the cockpit window }; //######################################################################## diff --git a/pack-dist.ps1 b/pack-dist.ps1 index fe916c1..332fa1c 100644 --- a/pack-dist.ps1 +++ b/pack-dist.ps1 @@ -234,7 +234,10 @@ RP412STEAM=1 Set-Content -Path "$dist\start-windowed.bat" -Encoding ascii -Value @" @echo off rem Red Planet 4.12 - desktop prototype. Boots into the game-setup -rem front end; the cockpit canvas is 1920x1080 and -res matches it. +rem front end. The cockpit fits itself to the window and keeps 16:9, +rem so a wider screen letterboxes rather than stretching. -res sets the +rem 3D render size - raise it to match a big screen for sharper pixels, +rem e.g. -res 2560 1440. rem (Add -egg TEST.EGG to skip the menu and run the canned mission.) cd /d "%~dp0" start rpl4opt.exe -windowed -res 1920 1080 @@ -263,6 +266,11 @@ documented default layout on first run; delete it to restore). F1-F12 secondary / screen columns Alt+Q abort the mission (score banked) +The cockpit scales to whatever window you give it - maximise it and it +grows, keeping its 16:9 shape (an ultrawide gets black bars rather than +a stretched cockpit). The 3D itself renders at whatever -res says, so +on a big screen raise it to match: rpl4opt.exe -windowed -res 2560 1440. + The full pod cockpit comes up in a single window: three green MFDs across the top, the 3D viewscreen centered with the orange plasma glass at its left, and the lower MFDs flanking the portrait map. The red