Cockpit scales both ways and re-fits on resize, aspect locked
The canvas was capped at 100%, so a bigger monitor got a 1920x1080 cockpit in the corner of the screen and maximising did nothing - the layout only ran once at startup. Now the fit is one uniform scale with no ceiling, recomputed whenever the window changes size (maximise, restore, drag), and the canvas is centred in whatever client area it gets. Uniform scale is what locks the aspect: a wider-than-16:9 desktop letterboxes with even black bars instead of stretching the cockpit. Panes gained Resize() so the glass and its button banks re-scale with the canvas; the pixel buffers keep their native source resolution. Scaling up is free quality on the MFDs - their glass is a downscale of a native 640x480 channel until about 200%. Verified on the 3440x1392 ultrawide: opens at 125% (2400x1350) instead of 100%, maximises to 126% centred with even bars, a 1884x661 window fits 61% letterboxed left/right, 1084x961 fits 56% letterboxed top/ bottom, and a 2584x1461 client scales up to 134%. The 3D still renders at -res and D3D stretches it to the canvas, so raise -res to match a large screen for 1:1 pixels; start-windowed.bat and the README say so. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
+170
-55
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
//########################################################################
|
||||
|
||||
+9
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user