Split-screen UI system with full ultrawide and multi-aspect-ratio support

Screen resolution is now auto-detected from the monitor at startup
instead of being hardcoded to 1920x1080. This fixes rendering on
ultrawide (21:9), super-ultrawide (32:9), 16:10, and any other
aspect ratio -- both in singleplayer and split-screen multiplayer.

The 3D world renders at native resolution so the full monitor is used.
Flash UI is 16:9-fitted and centered inside each viewport, pillarboxed
on wide displays and letterboxed on tall ones. Logical game dimensions
(used for ortho projection and HUD layout) are computed proportionally
from the real screen aspect ratio, fixing the stretched world projection
and HUD that the old hardcoded 1280x720 caused on non-16:9 monitors.

GameRenderer::ComputeViewportForPlayer uses the actual backbuffer size
instead of the logical game size, which was causing split-screen
viewports to be sized incorrectly.

UIScene::render fits menus to 16:9 within each split viewport using
GetViewportRect + Fit16x9, keeping inventory/crafting/options screens
at their designed aspect ratio instead of stretching.

Panorama and MenuBackground render at full viewport size with proper
tile scaling so the background fills the entire area without gaps in
vertical split and quadrant layouts.

HUD tile rendering uses ComputeTileScale to uniformly scale the SWF
and show the bottom portion (hotbar, hearts, hunger) in horizontal
and quadrant splits. repositionHud passes visible SWF-space dimensions
to ActionScript for proper element centering within each viewport.

Chat and Tooltips overlays use ComputeTileScale and
ComputeSplitContentOffset to anchor correctly to the bottom of each
player's viewport tile.

Container menus apply Fit16x9 to pointer coordinate mapping so the
cursor tracks correctly in split-screen. getMouseToSWFScale moved out
of the header into the .cpp. Mouse input in onMouseTick is gated to
pad 0 since raw mouse deltas should only drive player 1.

All shared viewport math lives in UISplitScreenHelpers.h:
- GetViewportRect: origin and dimensions for any viewport type
- Fit16x9: aspect-correct fitting with centering offsets
- ComputeTileScale: uniform scale and Y-offset for tile rendering
- ComputeSplitContentOffset: content centering for overlay components
This commit is contained in:
MrTheShy 2026-03-07 02:02:08 +01:00
parent 8c3eaff0f4
commit 7cd990eefb
16 changed files with 339 additions and 190 deletions

View file

@ -481,25 +481,15 @@ void IUIScene_AbstractContainerMenu::onMouseTick()
#endif #endif
#ifdef _WINDOWS64 #ifdef _WINDOWS64
if (!g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive()) if (iPad == 0 && !g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive())
{ {
int deltaX = g_KBMInput.GetMouseDeltaX(); int deltaX = g_KBMInput.GetMouseDeltaX();
int deltaY = g_KBMInput.GetMouseDeltaY(); int deltaY = g_KBMInput.GetMouseDeltaY();
extern HWND g_hWnd; float scaleX, scaleY;
RECT rc; getMouseToSWFScale(scaleX, scaleY);
GetClientRect(g_hWnd, &rc);
int winW = rc.right - rc.left;
int winH = rc.bottom - rc.top;
if (winW > 0 && winH > 0)
{
float scaleX = (float)getMovieWidth() / (float)winW;
float scaleY = (float)getMovieHeight() / (float)winH;
vPointerPos.x += (float)deltaX * scaleX; vPointerPos.x += (float)deltaX * scaleX;
vPointerPos.y += (float)deltaY * scaleY; vPointerPos.y += (float)deltaY * scaleY;
}
if (deltaX != 0 || deltaY != 0) if (deltaX != 0 || deltaY != 0)
{ {

View file

@ -277,4 +277,5 @@ public:
virtual int getPad() = 0; virtual int getPad() = 0;
virtual int getMovieWidth() = 0; virtual int getMovieWidth() = 0;
virtual int getMovieHeight() = 0; virtual int getMovieHeight() = 0;
virtual void getMouseToSWFScale(float &scaleX, float &scaleY) = 0;
}; };

View file

@ -1,6 +1,7 @@
#include "stdafx.h" #include "stdafx.h"
#include "UI.h" #include "UI.h"
#include "UIComponent_Chat.h" #include "UIComponent_Chat.h"
#include "UISplitScreenHelpers.h"
#include "..\..\Minecraft.h" #include "..\..\Minecraft.h"
#include "..\..\Gui.h" #include "..\..\Gui.h"
@ -114,6 +115,7 @@ void UIComponent_Chat::render(S32 width, S32 height, C4JRender::eViewportType vi
S32 tileWidth = width; S32 tileWidth = width;
S32 tileHeight = height; S32 tileHeight = height;
bool needsYTile = false;
switch( viewport ) switch( viewport )
{ {
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
@ -121,22 +123,27 @@ void UIComponent_Chat::render(S32 width, S32 height, C4JRender::eViewportType vi
tileHeight = (S32)(ui.getScreenHeight()); tileHeight = (S32)(ui.getScreenHeight());
break; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = (S32)(ui.getScreenWidth()); tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2); needsYTile = true;
break; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT: case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2); needsYTile = true;
break; break;
} }
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight ); F32 scale;
ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart);
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
S32 contentOffX, contentOffY;
ComputeSplitContentOffset(viewport, m_movieWidth, m_movieHeight, scale, tileWidth, tileHeight, tileYStart, contentOffX, contentOffY);
xPos += contentOffX;
yPos += contentOffY;
ui.setupRenderPosition(xPos, yPos);
IggyPlayerDrawTilesStart ( getMovie() ); IggyPlayerDrawTilesStart ( getMovie() );

View file

@ -68,21 +68,26 @@ void UIComponent_MenuBackground::render(S32 width, S32 height, C4JRender::eViewp
break; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
tileWidth = (S32)(ui.getScreenWidth()); tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2); tileYStart = (S32)(ui.getScreenHeight() / 2);
break; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = (S32)(ui.getScreenWidth()); tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2); tileYStart = (S32)(ui.getScreenHeight() / 2);
break; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT: case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2); tileYStart = (S32)(ui.getScreenHeight() / 2);
break; break;
} }
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight ); F32 scaleW = (F32)(tileXStart + tileWidth) / (F32)m_movieWidth;
F32 scaleH = (F32)(tileYStart + tileHeight) / (F32)m_movieHeight;
F32 scale = (scaleW > scaleH) ? scaleW : scaleH;
if(scale < 1.0f) scale = 1.0f;
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
IggyPlayerDrawTilesStart ( getMovie() ); IggyPlayerDrawTilesStart ( getMovie() );
@ -98,6 +103,10 @@ void UIComponent_MenuBackground::render(S32 width, S32 height, C4JRender::eViewp
} }
else else
{ {
UIScene::render(width, height, viewport); if(m_bIsReloading) return;
if(!m_hasTickedOnce || !getMovie()) return;
ui.setupRenderPosition(0, 0);
IggyPlayerSetDisplaySize( getMovie(), (S32)ui.getScreenWidth(), (S32)ui.getScreenHeight() );
IggyPlayerDraw( getMovie() );
} }
} }

View file

@ -93,15 +93,27 @@ void UIComponent_Panorama::render(S32 width, S32 height, C4JRender::eViewportTyp
} }
ui.setupRenderPosition(xPos, yPos); ui.setupRenderPosition(xPos, yPos);
if((viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT) || (viewport == C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT))
{
// Need to render at full height, but only the left side of the scene
S32 tileXStart = 0; S32 tileXStart = 0;
S32 tileYStart = 0; S32 tileYStart = 0;
S32 tileWidth = width; S32 tileWidth = width;
S32 tileHeight = (S32)(ui.getScreenHeight()); S32 tileHeight = height;
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight ); if((viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT) || (viewport == C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT))
{
tileHeight = (S32)(ui.getScreenHeight());
}
else
{
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(ui.getScreenHeight() / 2);
}
F32 scaleW = (F32)(tileXStart + tileWidth) / (F32)m_movieWidth;
F32 scaleH = (F32)(tileYStart + tileHeight) / (F32)m_movieHeight;
F32 scale = (scaleW > scaleH) ? scaleW : scaleH;
if(scale < 1.0f) scale = 1.0f;
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
IggyPlayerDrawTilesStart ( getMovie() ); IggyPlayerDrawTilesStart ( getMovie() );
@ -117,16 +129,13 @@ void UIComponent_Panorama::render(S32 width, S32 height, C4JRender::eViewportTyp
} }
else else
{ {
// Need to render at full height, and full width. But compressed into the viewport if(m_bIsReloading) return;
IggyPlayerSetDisplaySize( getMovie(), ui.getScreenWidth(), ui.getScreenHeight()/2 ); if(!m_hasTickedOnce || !getMovie()) return;
ui.setupRenderPosition(0, 0);
IggyPlayerSetDisplaySize( getMovie(), (S32)ui.getScreenWidth(), (S32)ui.getScreenHeight() );
IggyPlayerDraw( getMovie() ); IggyPlayerDraw( getMovie() );
} }
} }
else
{
UIScene::render(width, height, viewport);
}
}
void UIComponent_Panorama::setPanorama(bool isDay) void UIComponent_Panorama::setPanorama(bool isDay)
{ {

View file

@ -1,6 +1,7 @@
#include "stdafx.h" #include "stdafx.h"
#include "UI.h" #include "UI.h"
#include "UIComponent_Tooltips.h" #include "UIComponent_Tooltips.h"
#include "UISplitScreenHelpers.h"
UIComponent_Tooltips::UIComponent_Tooltips(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer) UIComponent_Tooltips::UIComponent_Tooltips(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer)
{ {
@ -224,6 +225,7 @@ void UIComponent_Tooltips::render(S32 width, S32 height, C4JRender::eViewportTyp
S32 tileWidth = width; S32 tileWidth = width;
S32 tileHeight = height; S32 tileHeight = height;
bool needsYTile = false;
switch( viewport ) switch( viewport )
{ {
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
@ -231,22 +233,27 @@ void UIComponent_Tooltips::render(S32 width, S32 height, C4JRender::eViewportTyp
tileHeight = (S32)(ui.getScreenHeight()); tileHeight = (S32)(ui.getScreenHeight());
break; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = (S32)(ui.getScreenWidth()); tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2); needsYTile = true;
break; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT: case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2); needsYTile = true;
break; break;
} }
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight ); F32 scale;
ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart);
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
S32 contentOffX, contentOffY;
ComputeSplitContentOffset(viewport, m_movieWidth, m_movieHeight, scale, tileWidth, tileHeight, tileYStart, contentOffX, contentOffY);
xPos += contentOffX;
yPos += contentOffY;
ui.setupRenderPosition(xPos, yPos);
IggyPlayerDrawTilesStart ( getMovie() ); IggyPlayerDrawTilesStart ( getMovie() );

View file

@ -13,6 +13,7 @@
#include "..\..\EnderDragonRenderer.h" #include "..\..\EnderDragonRenderer.h"
#include "..\..\MultiPlayerLocalPlayer.h" #include "..\..\MultiPlayerLocalPlayer.h"
#include "UIFontData.h" #include "UIFontData.h"
#include "UISplitScreenHelpers.h"
#ifdef _WINDOWS64 #ifdef _WINDOWS64
#include "..\..\Windows64\KeyboardMouseInput.h" #include "..\..\Windows64\KeyboardMouseInput.h"
#endif #endif
@ -57,6 +58,8 @@ bool UIController::ms_bReloadSkinCSInitialised = false;
DWORD UIController::m_dwTrialTimerLimitSecs=DYNAMIC_CONFIG_DEFAULT_TRIAL_TIME; DWORD UIController::m_dwTrialTimerLimitSecs=DYNAMIC_CONFIG_DEFAULT_TRIAL_TIME;
// GetViewportRect and Fit16x9 are now in UISplitScreenHelpers.h
#ifdef _WINDOWS64 #ifdef _WINDOWS64
static UIControl_Slider *FindSliderById(UIScene *pScene, int sliderId) static UIControl_Slider *FindSliderById(UIScene *pScene, int sliderId)
{ {
@ -854,28 +857,15 @@ void UIController::tickInput()
S32 displayW = 0, displayH = 0; S32 displayW = 0, displayH = 0;
getRenderDimensions(vp, displayW, displayH); getRenderDimensions(vp, displayW, displayH);
S32 originX = 0, originY = 0; F32 vpOriginX, vpOriginY, vpW, vpH;
switch (vp) GetViewportRect(getScreenWidth(), getScreenHeight(), vp, vpOriginX, vpOriginY, vpW, vpH);
{ // All viewports use Fit16x9 for menu scenes
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: S32 fitW, fitH, fitOffsetX, fitOffsetY;
originX = (S32)(getScreenWidth() / 4); break; Fit16x9(vpW, vpH, fitW, fitH, fitOffsetX, fitOffsetY);
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: S32 originX = (S32)vpOriginX + fitOffsetX;
originX = (S32)(getScreenWidth() / 4); S32 originY = (S32)vpOriginY + fitOffsetY;
originY = (S32)(getScreenHeight() / 2); break; displayW = fitW;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: displayH = fitH;
originY = (S32)(getScreenHeight() / 4); break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
originX = (S32)(getScreenWidth() / 2);
originY = (S32)(getScreenHeight() / 4); break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
originX = (S32)(getScreenWidth() / 2); break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
originY = (S32)(getScreenHeight() / 2); break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
originX = (S32)(getScreenWidth() / 2);
originY = (S32)(getScreenHeight() / 2); break;
default: break; // FULLSCREEN and QUADRANT_TOP_LEFT: origin (0,0)
}
if (displayW > 0 && displayH > 0) if (displayW > 0 && displayH > 0)
{ {
@ -1609,74 +1599,48 @@ void UIController::renderScenes()
void UIController::getRenderDimensions(C4JRender::eViewportType viewport, S32 &width, S32 &height) void UIController::getRenderDimensions(C4JRender::eViewportType viewport, S32 &width, S32 &height)
{ {
switch( viewport ) F32 originX, originY, viewW, viewH;
GetViewportRect(getScreenWidth(), getScreenHeight(), viewport, originX, originY, viewW, viewH);
if(viewport == C4JRender::VIEWPORT_TYPE_FULLSCREEN)
{ {
case C4JRender::VIEWPORT_TYPE_FULLSCREEN: S32 offsetX, offsetY;
width = (S32)(getScreenWidth()); Fit16x9(viewW, viewH, width, height, offsetX, offsetY);
height = (S32)(getScreenHeight()); }
break; else
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: {
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: // Split-screen: use raw viewport dims — the SWF tiling code handles non-16:9
width = (S32)(getScreenWidth() / 2); width = (S32)viewW;
height = (S32)(getScreenHeight() / 2); height = (S32)viewH;
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
width = (S32)(getScreenWidth() / 2);
height = (S32)(getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
width = (S32)(getScreenWidth() / 2);
height = (S32)(getScreenHeight() / 2);
break;
} }
} }
void UIController::setupRenderPosition(C4JRender::eViewportType viewport) void UIController::setupRenderPosition(C4JRender::eViewportType viewport)
{
if(m_bCustomRenderPosition || m_currentRenderViewport != viewport)
{ {
m_currentRenderViewport = viewport; m_currentRenderViewport = viewport;
m_bCustomRenderPosition = false; m_bCustomRenderPosition = false;
S32 xPos = 0;
S32 yPos = 0; F32 vpOriginX, vpOriginY, vpW, vpH;
switch( viewport ) GetViewportRect(getScreenWidth(), getScreenHeight(), viewport, vpOriginX, vpOriginY, vpW, vpH);
S32 xPos, yPos;
if(viewport == C4JRender::VIEWPORT_TYPE_FULLSCREEN)
{ {
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: S32 fitW, fitH, fitOffsetX, fitOffsetY;
xPos = (S32)(getScreenWidth() / 4); Fit16x9(vpW, vpH, fitW, fitH, fitOffsetX, fitOffsetY);
break; xPos = (S32)vpOriginX + fitOffsetX;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: yPos = (S32)vpOriginY + fitOffsetY;
xPos = (S32)(getScreenWidth() / 4);
yPos = (S32)(getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
yPos = (S32)(getScreenHeight() / 4);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
xPos = (S32)(getScreenWidth() / 2);
yPos = (S32)(getScreenHeight() / 4);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
xPos = (S32)(getScreenWidth() / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
yPos = (S32)(getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
xPos = (S32)(getScreenWidth() / 2);
yPos = (S32)(getScreenHeight() / 2);
break;
} }
else
{
// Split-screen: position at viewport origin, no 16:9 fitting
xPos = (S32)vpOriginX;
yPos = (S32)vpOriginY;
}
m_tileOriginX = xPos; m_tileOriginX = xPos;
m_tileOriginY = yPos; m_tileOriginY = yPos;
setTileOrigin(xPos, yPos); setTileOrigin(xPos, yPos);
} }
}
void UIController::setupRenderPosition(S32 xOrigin, S32 yOrigin) void UIController::setupRenderPosition(S32 xOrigin, S32 yOrigin)
{ {

View file

@ -1,6 +1,7 @@
#include "stdafx.h" #include "stdafx.h"
#include "UI.h" #include "UI.h"
#include "UIScene.h" #include "UIScene.h"
#include "UISplitScreenHelpers.h"
#include "..\..\Lighting.h" #include "..\..\Lighting.h"
#include "..\..\LocalPlayer.h" #include "..\..\LocalPlayer.h"
@ -685,10 +686,24 @@ void UIScene::render(S32 width, S32 height, C4JRender::eViewportType viewport)
{ {
if(m_bIsReloading) return; if(m_bIsReloading) return;
if(!m_hasTickedOnce || !swf) return; if(!m_hasTickedOnce || !swf) return;
if(viewport != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
{
F32 originX, originY, viewW, viewH;
GetViewportRect(ui.getScreenWidth(), ui.getScreenHeight(), viewport, originX, originY, viewW, viewH);
S32 fitW, fitH, offsetX, offsetY;
Fit16x9(viewW, viewH, fitW, fitH, offsetX, offsetY);
ui.setupRenderPosition((S32)originX + offsetX, (S32)originY + offsetY);
IggyPlayerSetDisplaySize( swf, fitW, fitH );
IggyPlayerDraw( swf );
}
else
{
ui.setupRenderPosition(viewport); ui.setupRenderPosition(viewport);
IggyPlayerSetDisplaySize( swf, width, height ); IggyPlayerSetDisplaySize( swf, width, height );
IggyPlayerDraw( swf ); IggyPlayerDraw( swf );
} }
}
void UIScene::setOpacity(float percent) void UIScene::setOpacity(float percent)
{ {

View file

@ -1,6 +1,7 @@
#include "stdafx.h" #include "stdafx.h"
#include "UI.h" #include "UI.h"
#include "UIScene_AbstractContainerMenu.h" #include "UIScene_AbstractContainerMenu.h"
#include "UISplitScreenHelpers.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.inventory.h" #include "..\..\..\Minecraft.World\net.minecraft.world.inventory.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.item.h" #include "..\..\..\Minecraft.World\net.minecraft.world.item.h"
@ -187,6 +188,11 @@ void UIScene_AbstractContainerMenu::PlatformInitialize(int iPad, int startIndex)
IggyEvent mouseEvent; IggyEvent mouseEvent;
S32 width, height; S32 width, height;
m_parentLayer->getRenderDimensions(width, height); m_parentLayer->getRenderDimensions(width, height);
C4JRender::eViewportType vp = m_parentLayer->getViewport();
if(vp != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
Fit16x9(width, height);
S32 x = m_pointerPos.x*((float)width/m_movieWidth); S32 x = m_pointerPos.x*((float)width/m_movieWidth);
S32 y = m_pointerPos.y*((float)height/m_movieHeight); S32 y = m_pointerPos.y*((float)height/m_movieHeight);
IggyMakeEventMouseMove( &mouseEvent, x, y); IggyMakeEventMouseMove( &mouseEvent, x, y);
@ -212,6 +218,10 @@ void UIScene_AbstractContainerMenu::tick()
S32 width, height; S32 width, height;
m_parentLayer->getRenderDimensions(width, height); m_parentLayer->getRenderDimensions(width, height);
C4JRender::eViewportType vp = m_parentLayer->getViewport();
if(vp != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
Fit16x9(width, height);
S32 x = (S32)(m_pointerPos.x * ((float)width / m_movieWidth)); S32 x = (S32)(m_pointerPos.x * ((float)width / m_movieWidth));
S32 y = (S32)(m_pointerPos.y * ((float)height / m_movieHeight)); S32 y = (S32)(m_pointerPos.y * ((float)height / m_movieHeight));
@ -251,6 +261,27 @@ void UIScene_AbstractContainerMenu::render(S32 width, S32 height, C4JRender::eVi
m_needsCacheRendered = false; m_needsCacheRendered = false;
} }
void UIScene_AbstractContainerMenu::getMouseToSWFScale(float &scaleX, float &scaleY)
{
extern HWND g_hWnd;
RECT rc;
GetClientRect(g_hWnd, &rc);
int winW = rc.right - rc.left;
int winH = rc.bottom - rc.top;
if(winW <= 0 || winH <= 0) { scaleX = 1.0f; scaleY = 1.0f; return; }
S32 renderW, renderH;
C4JRender::eViewportType vp = GetParentLayer()->getViewport();
ui.getRenderDimensions(vp, renderW, renderH);
if(vp != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
Fit16x9(renderW, renderH);
float screenW = (float)ui.getScreenWidth();
float screenH = (float)ui.getScreenHeight();
scaleX = (float)m_movieWidth * screenW / ((float)renderW * (float)winW);
scaleY = (float)m_movieHeight * screenH / ((float)renderH * (float)winH);
}
void UIScene_AbstractContainerMenu::customDraw(IggyCustomDrawCallbackRegion *region) void UIScene_AbstractContainerMenu::customDraw(IggyCustomDrawCallbackRegion *region)
{ {
Minecraft *pMinecraft = Minecraft::GetInstance(); Minecraft *pMinecraft = Minecraft::GetInstance();

View file

@ -38,6 +38,7 @@ public:
int getPad() { return m_iPad; } int getPad() { return m_iPad; }
int getMovieWidth() { return m_movieWidth; } int getMovieWidth() { return m_movieWidth; }
int getMovieHeight() { return m_movieHeight; } int getMovieHeight() { return m_movieHeight; }
void getMouseToSWFScale(float &scaleX, float &scaleY);
bool getIgnoreInput() { return m_bIgnoreInput; } bool getIgnoreInput() { return m_bIgnoreInput; }
void setIgnoreInput(bool bVal) { m_bIgnoreInput=bVal; } void setIgnoreInput(bool bVal) { m_bIgnoreInput=bVal; }

View file

@ -1,6 +1,7 @@
#include "stdafx.h" #include "stdafx.h"
#include "UI.h" #include "UI.h"
#include "UIScene_HUD.h" #include "UIScene_HUD.h"
#include "UISplitScreenHelpers.h"
#include "BossMobGuiInfo.h" #include "BossMobGuiInfo.h"
#include "..\..\Minecraft.h" #include "..\..\Minecraft.h"
#include "..\..\MultiplayerLocalPlayer.h" #include "..\..\MultiplayerLocalPlayer.h"
@ -266,8 +267,6 @@ void UIScene_HUD::handleReload()
SetDisplayName(ProfileManager.GetDisplayName(m_iPad)); SetDisplayName(ProfileManager.GetDisplayName(m_iPad));
repositionHud();
SetTooltipsEnabled(((ui.GetMenuDisplayed(ProfileManager.GetPrimaryPad())) || (app.GetGameSettings(ProfileManager.GetPrimaryPad(),eGameSetting_Tooltips) != 0))); SetTooltipsEnabled(((ui.GetMenuDisplayed(ProfileManager.GetPrimaryPad())) || (app.GetGameSettings(ProfileManager.GetPrimaryPad(),eGameSetting_Tooltips) != 0)));
} }
@ -697,6 +696,7 @@ void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewpor
S32 tileWidth = width; S32 tileWidth = width;
S32 tileHeight = height; S32 tileHeight = height;
bool needsYTile = false;
switch( viewport ) switch( viewport )
{ {
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
@ -704,22 +704,24 @@ void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewpor
tileHeight = (S32)(ui.getScreenHeight()); tileHeight = (S32)(ui.getScreenHeight());
break; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = (S32)(ui.getScreenWidth()); tileWidth = (S32)(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2); needsYTile = true;
break; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT: case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2); needsYTile = true;
break; break;
} }
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight ); F32 scale;
ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart);
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
repositionHud(tileWidth, tileHeight, scale);
m_renderWidth = tileWidth; m_renderWidth = tileWidth;
m_renderHeight = tileHeight; m_renderHeight = tileHeight;
@ -784,34 +786,24 @@ void UIScene_HUD::handleTimerComplete(int id)
//setVisible(anyVisible); //setVisible(anyVisible);
} }
void UIScene_HUD::repositionHud() void UIScene_HUD::repositionHud(S32 tileWidth, S32 tileHeight, F32 scale)
{ {
if(!m_bSplitscreen) return; if(!m_bSplitscreen) return;
S32 width = 0; // Pass the visible tile area in SWF coordinates so ActionScript
S32 height = 0; // positions elements (crosshair, hotbar, etc.) centered in the
m_parentLayer->getRenderDimensions( width, height ); // actually visible region, not the raw viewport.
S32 visibleW = (S32)(tileWidth / scale);
S32 visibleH = (S32)(tileHeight / scale);
switch( m_parentLayer->getViewport() ) app.DebugPrintf(app.USER_SR, "Reposition HUD: tile %dx%d, scale %.3f, visible SWF %dx%d\n", tileWidth, tileHeight, scale, visibleW, visibleH );
{
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
height = (S32)(ui.getScreenHeight());
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
width = (S32)(ui.getScreenWidth());
break;
}
app.DebugPrintf(app.USER_SR, "Reposition HUD with dims %d, %d\n", width, height );
IggyDataValue result; IggyDataValue result;
IggyDataValue value[2]; IggyDataValue value[2];
value[0].type = IGGY_DATATYPE_number; value[0].type = IGGY_DATATYPE_number;
value[0].number = width; value[0].number = visibleW;
value[1].type = IGGY_DATATYPE_number; value[1].type = IGGY_DATATYPE_number;
value[1].number = height; value[1].number = visibleH;
IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcRepositionHud , 2 , value ); IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcRepositionHud , 2 , value );
} }

View file

@ -176,5 +176,5 @@ protected:
#endif #endif
private: private:
void repositionHud(); void repositionHud(S32 tileWidth, S32 tileHeight, F32 scale);
}; };

View file

@ -0,0 +1,114 @@
#pragma once
// Shared split-screen UI helpers to avoid duplicating viewport math
// across HUD, Chat, Tooltips, and container menus.
// Compute the raw viewport rectangle for a given viewport type.
inline void GetViewportRect(F32 screenW, F32 screenH, C4JRender::eViewportType viewport,
F32 &originX, F32 &originY, F32 &viewW, F32 &viewH)
{
originX = originY = 0;
viewW = screenW;
viewH = screenH;
switch(viewport)
{
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
originY = screenH * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
viewW = screenW * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
originX = screenW * 0.5f; viewW = screenW * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
originX = screenW * 0.5f; viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
originY = screenH * 0.5f; viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
originX = screenW * 0.5f; originY = screenH * 0.5f;
viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
default: break;
}
}
// Fit a 16:9 rectangle inside the given dimensions.
inline void Fit16x9(F32 viewW, F32 viewH, S32 &fitW, S32 &fitH, S32 &offsetX, S32 &offsetY)
{
const F32 kAspect = 16.0f / 9.0f;
if(viewW / viewH > kAspect)
{
fitH = (S32)viewH;
fitW = (S32)(viewH * kAspect);
}
else
{
fitW = (S32)viewW;
fitH = (S32)(viewW / kAspect);
}
offsetX = (S32)((viewW - fitW) * 0.5f);
offsetY = (S32)((viewH - fitH) * 0.5f);
}
// Convenience: just fit 16:9 dimensions, ignore offsets.
inline void Fit16x9(S32 &width, S32 &height)
{
S32 offX, offY;
Fit16x9((F32)width, (F32)height, width, height, offX, offY);
}
// Compute the uniform scale and tileYStart for split-screen tile rendering.
// Used by HUD, Chat, and Tooltips to scale the SWF movie to cover the viewport tile.
inline void ComputeTileScale(S32 tileWidth, S32 tileHeight, S32 movieWidth, S32 movieHeight,
bool needsYTile, F32 &outScale, S32 &outTileYStart)
{
F32 scaleW = (F32)tileWidth / (F32)movieWidth;
F32 scaleH = (F32)tileHeight / (F32)movieHeight;
F32 scale = (scaleW > scaleH) ? scaleW : scaleH;
if(scale < 1.0f) scale = 1.0f;
outTileYStart = 0;
if(needsYTile)
{
S32 dispH = (S32)(movieHeight * scale);
outTileYStart = dispH - tileHeight;
if(outTileYStart < 0) outTileYStart = 0;
scaleH = (F32)(outTileYStart + tileHeight) / (F32)movieHeight;
scale = (scaleW > scaleH) ? scaleW : scaleH;
if(scale < 1.0f) scale = 1.0f;
}
outScale = scale;
}
// Compute the render offset to center split-screen SWF content in the viewport.
// Used by Chat and Tooltips (HUD uses repositionHud instead).
inline void ComputeSplitContentOffset(C4JRender::eViewportType viewport, S32 movieWidth, S32 movieHeight,
F32 scale, S32 tileWidth, S32 tileHeight, S32 tileYStart,
S32 &outXOffset, S32 &outYOffset)
{
S32 contentCenterX, contentCenterY;
if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT || viewport == C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT)
{
contentCenterX = (S32)(movieWidth * scale / 4);
contentCenterY = (S32)(movieHeight * scale / 2);
}
else if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_TOP || viewport == C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM)
{
contentCenterX = (S32)(movieWidth * scale / 2);
contentCenterY = (S32)(movieHeight * scale * 3 / 4);
}
else
{
contentCenterX = (S32)(movieWidth * scale / 4);
contentCenterY = (S32)(movieHeight * scale * 3 / 4);
}
outXOffset = 0;
outYOffset = 0;
if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT)
outXOffset = -(tileWidth / 2 - contentCenterX);
if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_TOP || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT)
outYOffset = -(tileHeight / 2 - (contentCenterY - tileYStart));
}

View file

@ -960,6 +960,10 @@ void GameRenderer::CachePlayerGammas()
bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport) const bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport) const
{ {
// Use the actual backbuffer dimensions, not the game's logical size.
extern int g_iScreenWidth;
extern int g_iScreenHeight;
int active = 0; int active = 0;
int indexMap[NUM_LIGHT_TEXTURES] = {-1, -1, -1, -1}; int indexMap[NUM_LIGHT_TEXTURES] = {-1, -1, -1, -1};
for (int i = 0; i < XUSER_MAX_COUNT && i < NUM_LIGHT_TEXTURES; ++i) for (int i = 0; i < XUSER_MAX_COUNT && i < NUM_LIGHT_TEXTURES; ++i)
@ -972,8 +976,8 @@ bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport)
{ {
outViewport.TopLeftX = 0.0f; outViewport.TopLeftX = 0.0f;
outViewport.TopLeftY = 0.0f; outViewport.TopLeftY = 0.0f;
outViewport.Width = static_cast<FLOAT>(mc->width); outViewport.Width = static_cast<FLOAT>(g_iScreenWidth);
outViewport.Height = static_cast<FLOAT>(mc->height); outViewport.Height = static_cast<FLOAT>(g_iScreenHeight);
outViewport.MinDepth = 0.0f; outViewport.MinDepth = 0.0f;
outViewport.MaxDepth = 1.0f; outViewport.MaxDepth = 1.0f;
return true; return true;
@ -989,8 +993,8 @@ bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport)
if (k < 0) if (k < 0)
return false; return false;
const float width = static_cast<float>(mc->width); const float width = static_cast<float>(g_iScreenWidth);
const float height = static_cast<float>(mc->height); const float height = static_cast<float>(g_iScreenHeight);
if (active == 2) if (active == 2)
{ {

View file

@ -4590,8 +4590,14 @@ void Minecraft::startAndConnectTo(const wstring& name, const wstring& sid, const
Minecraft *minecraft; Minecraft *minecraft;
// 4J - was new Minecraft(frame, canvas, NULL, 854, 480, fullScreen); // 4J - was new Minecraft(frame, canvas, NULL, 854, 480, fullScreen);
// Logical width is proportional to the real screen aspect ratio so that
// the ortho projection and HUD layout match the viewport without stretching.
extern int g_iScreenWidth;
extern int g_iScreenHeight;
int logicalH = 720;
int logicalW = logicalH * g_iScreenWidth / g_iScreenHeight;
minecraft = new Minecraft(NULL, NULL, NULL, 1280, 720, fullScreen); minecraft = new Minecraft(NULL, NULL, NULL, logicalW, logicalH, fullScreen);
/* - 4J - removed /* - 4J - removed
{ {

View file

@ -86,12 +86,9 @@ DWORD dwProfileSettingsA[NUM_PROFILE_VALUES]=
BOOL g_bWidescreen = TRUE; BOOL g_bWidescreen = TRUE;
// DO NOT auto-detect from the monitor (GetSystemMetrics etc.) — the game resolution // Screen resolution — auto-detected from the monitor at startup.
// must stay 1920x1080. The precompiled 4J_Render lib, all Flash SWF assets, and the // The 3D world renders at native resolution; Flash UI is 16:9-fitted and centered
// split-screen viewport math (RenderManager.StateSetViewport, UIController::getRenderDimensions, // within each viewport (pillarboxed on ultrawide, letterboxed on tall displays).
// setupRenderPosition) all assume a 16:9 framebuffer. Using the native monitor resolution
// breaks split-screen on ultrawide/non-16:9 displays: the UI renders offset and the
// bottom portion of each viewport is left empty.
// ApplyScreenMode() can still override these for debug/test resolutions via launch args. // ApplyScreenMode() can still override these for debug/test resolutions via launch args.
int g_iScreenWidth = 1920; int g_iScreenWidth = 1920;
int g_iScreenHeight = 1080; int g_iScreenHeight = 1080;
@ -1184,8 +1181,10 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
if (pSlash) { *(pSlash + 1) = '\0'; SetCurrentDirectoryA(szExeDir); } if (pSlash) { *(pSlash + 1) = '\0'; SetCurrentDirectoryA(szExeDir); }
} }
// Declare DPI awareness so pixel coordinates are consistent // Declare DPI awareness so GetSystemMetrics returns physical pixels
SetProcessDPIAware(); SetProcessDPIAware();
g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
g_iScreenHeight = GetSystemMetrics(SM_CYSCREEN);
// Load username from username.txt // Load username from username.txt
char exePath[MAX_PATH] = {}; char exePath[MAX_PATH] = {};