diff --git a/BUILD.md b/BUILD.md index 1b24f82..d8571e1 100644 --- a/BUILD.md +++ b/BUILD.md @@ -114,17 +114,27 @@ Two new environment options remove the hardware dependency entirely: desktop window ("Plasma Display", plasma orange, `L4PLASMASCALE` sets the pixel size, default 4). It opens directly below the main view; `L4PLASMAPOS=x,y` overrides. No COM port. Closing the window hides it. -- **`L4MFDSPLIT=1`** — un-packs the 7-display cockpit. The pod hardware - drove five monochrome MFDs from the color channels of two video outputs - (window 3 = upper MFDs in R/G/B, window 4 = lower MFDs in R/G) and - mounted the map display portrait. With split mode the game itself opens - seven proper windows — main view, five green-screen MFDs ("MFD upper - left/center/right", "MFD lower left/right") and the 90°-rotated "Map" — - rendered CPU-side from the shared gauge canvas; the packed windows stay - hidden. `L4MFDSCALE` sets the view size in percent of the gauge canvas - (default 50). Default layout is the pod grid to the right of the main - view; all windows are draggable. This replaces the external - BitBlt-mirror launcher wrapper. +- **`L4MFDSPLIT=1`** — assembles the **whole pod interior in one window**. + The pod hardware drove five monochrome MFDs from the color channels of + two video outputs (window 3 = upper MFDs in R/G/B, window 4 = lower + MFDs in R/G) and mounted the map display portrait. In split mode the + main game window becomes the cockpit shell and every display is a + chrome-less child pane, arranged as in the pod: + + ``` + [ MFD UL ] [ MFD UC ] [ MFD UR ] + [ plasma (reduced) ][ viewscreen (centered) ] + [ MFD LL ] [ Map ] [ MFD LR ] + ``` + + MFDs render green-screen and the map full-color/rotated, CPU-side from + the shared gauge canvas (the packed D3D windows stay hidden); the 3D + scene presents into the viewscreen pane via `Present`'s + `hDestWindowOverride` (`gMainPresentWindow`), so no swap-chain changes. + Mouse clicks over the viewscreen fall through to the game window + (STATIC pane hit-test transparency). `L4MFDSCALE` sets the MFD/map pane + size in percent of the gauge canvas (default 50). This replaces the + external BitBlt-mirror launcher wrapper. Each split window also carries its display's **physical button bank** (geometry per vRIO's `CockpitLayout`): 4 red buttons above and below diff --git a/MUNGA_L4/L4MFDVIEW.cpp b/MUNGA_L4/L4MFDVIEW.cpp index 178528b..c73daf3 100644 --- a/MUNGA_L4/L4MFDVIEW.cpp +++ b/MUNGA_L4/L4MFDVIEW.cpp @@ -61,6 +61,7 @@ namespace view->Paint(); return 0; } + // no instance (e.g. the plain viewscreen child): default paint break; case WM_LBUTTONDOWN: @@ -86,7 +87,11 @@ namespace return 0; case WM_ERASEBKGND: - return 1; + if (view != NULL) + { + return 1; + } + break; // viewscreen child: erase with the class black brush } return DefWindowProcA(hwnd, message, wParam, lParam); } @@ -106,7 +111,8 @@ MFDSplitView::MFDSplitView( int y, ButtonStyle button_style, int anchorA, - int anchorB + int anchorB, + void *parent ) { Check_Pointer(this); @@ -133,6 +139,8 @@ MFDSplitView::MFDSplitView( int client_height = display_height; LayoutButtons(button_style, anchorA, anchorB, display_width, display_height, &client_width, &client_height); + clientWidth = client_width; + clientHeight = client_height; HINSTANCE instance = GetModuleHandleA(NULL); @@ -152,13 +160,28 @@ MFDSplitView::MFDSplitView( RegisterClassA(&window_class); } - DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; - RECT bounds; - bounds.left = 0; - bounds.top = 0; - bounds.right = client_width; - bounds.bottom = client_height; - AdjustWindowRect(&bounds, style, FALSE); + DWORD style; + int window_w, window_h; + + if (parent != NULL) + { + // chrome-less pane inside the cockpit window + style = WS_CHILD | WS_VISIBLE; + window_w = client_width; + window_h = client_height; + } + else + { + style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + RECT bounds; + bounds.left = 0; + bounds.top = 0; + bounds.right = client_width; + bounds.bottom = client_height; + AdjustWindowRect(&bounds, style, FALSE); + window_w = bounds.right - bounds.left; + window_h = bounds.bottom - bounds.top; + } window = CreateWindowExA( 0, @@ -166,9 +189,9 @@ MFDSplitView::MFDSplitView( title, style, x, y, - bounds.right - bounds.left, - bounds.bottom - bounds.top, - NULL, NULL, instance, NULL + window_w, + window_h, + (HWND) parent, NULL, instance, NULL ); if (window != NULL) diff --git a/MUNGA_L4/L4MFDVIEW.h b/MUNGA_L4/L4MFDVIEW.h index 0e2f7df..6c3f5cd 100644 --- a/MUNGA_L4/L4MFDVIEW.h +++ b/MUNGA_L4/L4MFDVIEW.h @@ -35,6 +35,9 @@ public: SideColumns // 6 per side; anchorA = left base, anchorB = right base }; + // With a parent, the view is created as a chrome-less child window + // inside the single cockpit window (x/y in parent client coords); + // without one it is its own draggable desktop window. MFDSplitView( const char *title, int source_width, @@ -45,9 +48,17 @@ public: int y, ButtonStyle button_style = NoButtons, int anchorA = 0, - int anchorB = 0 + int anchorB = 0, + void *parent = 0 ); + int + ClientWidth() const + { return clientWidth; } + int + ClientHeight() const + { return clientHeight; } + ~MFDSplitView(); Logical @@ -102,6 +113,8 @@ protected: int displayX, displayY, displayW, displayH; + int + clientWidth, clientHeight; ScreenButton buttons[maxButtons]; diff --git a/MUNGA_L4/L4PLASMASCREEN.cpp b/MUNGA_L4/L4PLASMASCREEN.cpp index 03918b3..81e81dd 100644 --- a/MUNGA_L4/L4PLASMASCREEN.cpp +++ b/MUNGA_L4/L4PLASMASCREEN.cpp @@ -64,27 +64,42 @@ namespace PlasmaScreen *PlasmaScreen::activeInstance = NULL; void - PlasmaScreen::Position(int x, int y, int client_width, int client_height) + PlasmaScreen::Position(void *parent, int x, int y, int client_width, int client_height) { if (activeInstance == NULL || activeInstance->window == NULL) { return; } - DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; - RECT bounds; - bounds.left = 0; - bounds.top = 0; - bounds.right = client_width; - bounds.bottom = client_height; - AdjustWindowRect(&bounds, style, FALSE); + HWND glass = (HWND) activeInstance->window; + int window_w = client_width; + int window_h = client_height; + + if (parent != NULL) + { + // fold the glass into the cockpit window as a chrome-less pane + SetWindowLongPtrA(glass, GWL_STYLE, WS_CHILD | WS_VISIBLE); + SetParent(glass, (HWND) parent); + } + else + { + DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + RECT bounds; + bounds.left = 0; + bounds.top = 0; + bounds.right = client_width; + bounds.bottom = client_height; + AdjustWindowRect(&bounds, style, FALSE); + window_w = bounds.right - bounds.left; + window_h = bounds.bottom - bounds.top; + } SetWindowPos( - (HWND) activeInstance->window, NULL, + glass, NULL, x, y, - bounds.right - bounds.left, - bounds.bottom - bounds.top, - SWP_NOZORDER | SWP_NOACTIVATE + window_w, + window_h, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW ); } diff --git a/MUNGA_L4/L4PLASMASCREEN.h b/MUNGA_L4/L4PLASMASCREEN.h index fb3faa9..0663276 100644 --- a/MUNGA_L4/L4PLASMASCREEN.h +++ b/MUNGA_L4/L4PLASMASCREEN.h @@ -38,10 +38,11 @@ public: WaitForUpdate(); // Reposition/resize the live glass (client-area size; the paint - // stretches to fit, so this also rescales it). Used by SVGA16's - // split-view layout; no-op when no plasma screen exists. + // stretches to fit, so this also rescales it). With a parent the + // glass becomes a chrome-less child pane of the cockpit window. + // Used by SVGA16's split-view layout; no-op when no plasma exists. static void - Position(int x, int y, int client_width, int client_height); + Position(void *parent, int x, int y, int client_width, int client_height); protected: static PlasmaScreen diff --git a/MUNGA_L4/L4VB16.cpp b/MUNGA_L4/L4VB16.cpp index 9495f9f..5ada290 100644 --- a/MUNGA_L4/L4VB16.cpp +++ b/MUNGA_L4/L4VB16.cpp @@ -3821,6 +3821,7 @@ SVGA16::SVGA16( // windows can stay hidden. //------------------------------------------------------------------ splitViews = False; + cockpitViewscreen = NULL; { const char *split_string = getenv("L4MFDSPLIT"); if (split_string != NULL && atoi(split_string) != 0) @@ -3857,51 +3858,68 @@ SVGA16::SVGA16( int cell_w = (init_width * scale_percent) / 100; int cell_h = (init_height * scale_percent) / 100; - int frame_h = GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYFIXEDFRAME); - int frame_w = 2 * GetSystemMetrics(SM_CXFIXEDFRAME); //--------------------------------------------------------------- - // Emulate the pod interior: + // Assemble the pod interior in ONE window (the main game window + // becomes the cockpit shell; every display is a chrome-less + // child pane): // [ MFD UL ] [ MFD UC ] [ MFD UR ] - // [ plasma (reduced) ][ main screen (centered) ] + // [ plasma (reduced) ][ viewscreen (centered) ] // [ MFD LL ] [ Map ] [ MFD LR ] - // The main game window is moved into the middle band; the plasma - // glass shrinks to fill the space at its left. + // The 3D scene presents into the viewscreen child via + // gMainPresentWindow. //--------------------------------------------------------------- + int gap = 8; + + // pane sizes mirror MFDSplitView::LayoutButtons (buttonGap = 4) int strip_h = cell_h / 8; if (strip_h < 18) strip_h = 18; if (strip_h > 40) strip_h = 40; - int mfd_extra_h = 2 * (strip_h + 8); + int block_h = cell_h + 2 * (strip_h + 8); - int col_span = cell_w + frame_w; - int row_w = 3 * col_span; - int band_y = cell_h + mfd_extra_h + frame_h; // below the upper row + int map_col_w = cell_h / 8; + if (map_col_w < 20) map_col_w = 20; + if (map_col_w > 40) map_col_w = 40; + int map_block_w = cell_h + 2 * (map_col_w + 8); + int map_block_h = cell_w; + int col_span = cell_w + gap; + int row_w = 3 * cell_w + 2 * gap; + + int band_y = block_h + gap; int main_w = (int) L4Application::GetScreenWidth(); int main_h = (int) L4Application::GetScreenHeight(); + + // center the viewscreen under the columns, but always leave the + // plasma glass at least 128px of space at its left int main_x = (row_w - main_w) / 2; - if (main_x < 0) main_x = 0; + int plasma_min = 128 + 2 * gap; + if (main_x < plasma_min) main_x = plasma_min; - HWND main_window = ApplicationManager::GetCurrentManager()->GetHWnd(); - if (main_window != NULL) + int lower_y = band_y + main_h + gap; + int lower_h = (block_h > map_block_h) ? block_h : map_block_h; + int client_w = (row_w > main_x + main_w) ? row_w : (main_x + main_w); + int client_h = lower_y + lower_h; + + //--------------------------------------------------------------- + // Grow the main window into the cockpit shell + //--------------------------------------------------------------- + HWND cockpit = ApplicationManager::GetCurrentManager()->GetHWnd(); + if (cockpit != NULL) { - SetWindowPos(main_window, NULL, main_x, band_y, 0, 0, - SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); - } + SetWindowLongPtrA(cockpit, GWL_STYLE, + GetWindowLongPtrA(cockpit, GWL_STYLE) | WS_CLIPCHILDREN); - // plasma: reduced to fit left of the main screen, centered - // vertically in the band (128x32 glass aspect) - int plasma_avail = main_x - 12; - if (plasma_avail >= 128) - { - int glass_w = plasma_avail; - int glass_h = glass_w / 4; - PlasmaScreen::Position( - 4, band_y + (main_h - glass_h - frame_h) / 2, - glass_w, glass_h); - } + RECT outer, inner; + GetWindowRect(cockpit, &outer); + GetClientRect(cockpit, &inner); + int chrome_w = (outer.right - outer.left) - inner.right; + int chrome_h = (outer.bottom - outer.top) - inner.bottom; - int lower_y = band_y + main_h; + SetWindowPos(cockpit, NULL, 0, 0, + client_w + chrome_w, client_h + chrome_h, + SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } //--------------------------------------------------------------- // Button banks per display (addresses per vRIO CockpitLayout, @@ -3913,36 +3931,59 @@ SVGA16::SVGA16( splitView[SplitMFDUpperLeft] = new MFDSplitView( "MFD upper left", init_width, init_height, cell_w, cell_h, 0, 0, - MFDSplitView::MFDStrips, 0x2F); + MFDSplitView::MFDStrips, 0x2F, 0, cockpit); splitView[SplitMFDUpperCenter] = new MFDSplitView( "MFD upper center", init_width, init_height, cell_w, cell_h, col_span, 0, - MFDSplitView::MFDStrips, 0x27); + MFDSplitView::MFDStrips, 0x27, 0, cockpit); splitView[SplitMFDUpperRight] = new MFDSplitView( "MFD upper right", init_width, init_height, cell_w, cell_h, 2 * col_span, 0, - MFDSplitView::MFDStrips, 0x37); + MFDSplitView::MFDStrips, 0x37, 0, cockpit); splitView[SplitMFDLowerLeft] = new MFDSplitView( "MFD lower left", init_width, init_height, cell_w, cell_h, 0, lower_y, - MFDSplitView::MFDStrips, 0x0F); + MFDSplitView::MFDStrips, 0x0F, 0, cockpit); splitView[SplitMFDLowerRight] = new MFDSplitView( "MFD lower right", init_width, init_height, cell_w, cell_h, 2 * col_span, lower_y, - MFDSplitView::MFDStrips, 0x07); - // map is portrait: source rotated 90 degrees clockwise; shift left - // by one side-button column so the GLASS (not the window) centers - // under the upper-center MFD - int map_col_w = cell_h / 8; - if (map_col_w < 20) map_col_w = 20; - if (map_col_w > 40) map_col_w = 40; + MFDSplitView::MFDStrips, 0x07, 0, cockpit); + // map is portrait: source rotated 90 degrees clockwise, its pane + // centered under the middle column splitView[SplitMap] = new MFDSplitView( "Map", init_height, init_width, cell_h, cell_w, - col_span + (cell_w - cell_h) / 2 - (map_col_w + 8), lower_y, - MFDSplitView::SideColumns, 0x10, 0x18); + col_span + (cell_w - map_block_w) / 2, lower_y, + MFDSplitView::SideColumns, 0x10, 0x18, cockpit); - DEBUG_STREAM << "SVGA16: split views active - 5 MFD windows + rotated map, cockpit buttons on (scale " + //--------------------------------------------------------------- + // The viewscreen: a black STATIC pane the 3D scene presents + // into. STATIC hit-tests transparent, so mouse input over the + // 3D view still reaches the game window as before. + //--------------------------------------------------------------- + if (cockpit != NULL) + { + cockpitViewscreen = CreateWindowExA( + 0, "STATIC", "", + WS_CHILD | WS_VISIBLE | SS_BLACKRECT, + main_x, band_y, main_w, main_h, + cockpit, NULL, GetModuleHandleA(NULL), NULL); + if (cockpitViewscreen != NULL) + { + gMainPresentWindow = cockpitViewscreen; + } + + // plasma glass, reduced to fill the space left of the + // viewscreen, vertically centered in the band + int glass_w = main_x - 2 * gap; + int glass_h = glass_w / 4; + PlasmaScreen::Position(cockpit, + gap, band_y + (main_h - glass_h) / 2, + glass_w, glass_h); + } + + DEBUG_STREAM << "SVGA16: single-window cockpit assembled (" + << client_w << "x" << client_h << ", scale " << scale_percent << "%)\n" << std::flush; } //STUBBED: VIDEO RB 1/15/07 @@ -4134,6 +4175,15 @@ SVGA16::~SVGA16() delete splitView[view]; splitView[view] = NULL; } + if (cockpitViewscreen != NULL) + { + if (gMainPresentWindow == cockpitViewscreen) + { + gMainPresentWindow = NULL; + } + DestroyWindow(cockpitViewscreen); + cockpitViewscreen = NULL; + } Check_Fpu(); } diff --git a/MUNGA_L4/L4VB16.h b/MUNGA_L4/L4VB16.h index 8fc93ae..f2d0a49 100644 --- a/MUNGA_L4/L4VB16.h +++ b/MUNGA_L4/L4VB16.h @@ -319,6 +319,8 @@ private: splitViews; MFDSplitView *splitView[SplitViewCount]; + HWND + cockpitViewscreen; // child pane the 3D scene presents into }; //######################################################################## diff --git a/MUNGA_L4/L4VIDEO.cpp b/MUNGA_L4/L4VIDEO.cpp index 1762479..10f5626 100644 --- a/MUNGA_L4/L4VIDEO.cpp +++ b/MUNGA_L4/L4VIDEO.cpp @@ -27,6 +27,10 @@ using namespace std; LPDIRECT3D9 gD3D = NULL; +// Single-window cockpit: viewscreen child window the scene presents into +// (NULL = present to the device window as always). +HWND gMainPresentWindow = NULL; + //STUBBED: DPL RB 1/14/07 // when this is resolved it can be removed #include "..\DPLSTUB.h" @@ -6152,7 +6156,7 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte hr = mDevice->EndScene(); - hr = mDevice->Present(NULL, NULL, NULL, NULL); + hr = mDevice->Present(NULL, NULL, gMainPresentWindow, NULL); if (hr == D3DERR_DEVICELOST) { int bbCount = mPresentParams.BackBufferCount; @@ -6187,7 +6191,7 @@ void DPLRenderer::ExecuteIdle() hr = mDevice->EndScene(); - if (mDevice->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST) + if (mDevice->Present(NULL, NULL, gMainPresentWindow, NULL) == D3DERR_DEVICELOST) { int bbCount = mPresentParams.BackBufferCount; int bbWidth = mPresentParams.BackBufferWidth; diff --git a/MUNGA_L4/L4VIDEO.h b/MUNGA_L4/L4VIDEO.h index 8a182cf..897f850 100644 --- a/MUNGA_L4/L4VIDEO.h +++ b/MUNGA_L4/L4VIDEO.h @@ -28,6 +28,11 @@ using namespace stdext; class NotationFile; +// Single-window cockpit (L4MFDSPLIT): when non-NULL, the main 3D scene +// presents into this child "viewscreen" window instead of stretching to +// the device window. Set by SVGA16 when it assembles the cockpit. +extern HWND gMainPresentWindow; + #define INTERSECT_ALL (0xFFFFFFFF) //########################################################################## diff --git a/RP_L4/RPL4.CPP b/RP_L4/RPL4.CPP index 16e522b..2fadf2c 100644 --- a/RP_L4/RPL4.CPP +++ b/RP_L4/RPL4.CPP @@ -164,7 +164,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine wc.hInstance = hInstance; wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION); wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = L"MainWndClass"; diff --git a/docs/RP412-ROADMAP.md b/docs/RP412-ROADMAP.md index 09708ec..8ffd5ef 100644 --- a/docs/RP412-ROADMAP.md +++ b/docs/RP412-ROADMAP.md @@ -50,12 +50,15 @@ so press-feedback works like the real button field. `Video8BitBuffered` surface into a desktop "Plasma Display" window in plasma orange — verified drawing live game content (score readout). See BUILD.md §4 for bindings and the desktop `environ.ini`. -- ✅ **7-display cockpit in-engine** (`L4MFDSPLIT=1`): the five MFDs the pod - packed into the color channels of two video outputs, plus the - portrait-mounted map, now open as their own windows (green-screen MFDs, - 90°-rotated map), rendered CPU-side from the shared gauge canvas — the - external BitBlt-mirror wrapper is obsolete. Verified visually (MFD score - readout, full tactical map). +- ✅ **7-display cockpit in-engine, single window** (`L4MFDSPLIT=1`): the + five channel-packed MFDs, the portrait map, the plasma glass and the 3D + viewscreen all assemble inside one cockpit window in the pod interior + arrangement (MFD row / plasma+viewscreen band / MFD-map row), with each + display's physical button bank around it. The 3D scene presents into + the viewscreen child pane; MFDs/map render CPU-side from the gauge + canvas. The external BitBlt-mirror wrapper is obsolete. Verified + visually: live gauges (LIFT CUT/BOOST/CHUTE), tactical map with lit + preset lamps, 3D scene in the viewscreen. - ✅ **Cockpit buttons on the displays** (2026-07-12): each split window carries its physical button bank — 4+4 red buttons per MFD, 6 amber per map side (Secondary/Screen columns; addresses per vRIO `CockpitLayout`, diff --git a/pack-dist.ps1 b/pack-dist.ps1 index 6071251..15210dd 100644 --- a/pack-dist.ps1 +++ b/pack-dist.ps1 @@ -95,12 +95,12 @@ Controls (XInput controller and/or keyboard): DPad / arrow keys joystick hat (look) Start,Back / F1,F2 config buttons -The full pod cockpit comes up as separate windows: the main view, five -green MFD screens, the portrait map, and the orange plasma glass below -the main view. Drag them wherever you like; closing one just hides it. -The red buttons around each MFD and the amber buttons flanking the map -are the pod's real button banks: click them with the mouse, and they -light up as the game commands their lamps. +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 +buttons around each MFD and the amber buttons beside the map are the +pod's real button banks: click them with the mouse, and they light up +as the game commands their lamps. environ.ini options: L4PADFLIP=XY inverts the stick axes, L4MFDSCALE=n sizes the MFD/map windows (percent, default 50), L4PLASMASCALE=n sets the plasma pixel size (default 4), L4PLASMAPOS=x,y places the plasma.