diff --git a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp index fed670b4e35..48798f9105a 100644 --- a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp @@ -739,6 +739,12 @@ void ControlBarScheme::update() } +// the right and bottom edge of an image on the last pixel of the creation resolution reach the screen edge at any resolution +static Real schemeEdge( Int edge, Int creationRes, Real multi, Int offset ) +{ + return (edge == creationRes - 1 ? creationRes : edge) * multi + offset; +} + // // Loop through the proper lists and draw everything for the foreground //----------------------------------------------------------------------------- @@ -767,8 +773,8 @@ void ControlBarScheme::drawForeground( Coord2D multi, ICoord2D offset ) // draw the image TheDisplay->drawImage(schemeImage->m_image, schemeImage->m_position.x * multi.x + offset.x, schemeImage->m_position.y * multi.y + offset.y, - (schemeImage->m_position.x + schemeImage->m_size.x) * multi.x + offset.x, - (schemeImage->m_position.y + schemeImage->m_size.y) * multi.y + offset.y); + schemeEdge(schemeImage->m_position.x + schemeImage->m_size.x, m_ScreenCreationRes.x, multi.x, offset.x), + schemeEdge(schemeImage->m_position.y + schemeImage->m_size.y, m_ScreenCreationRes.y, multi.y, offset.y)); it ++; } @@ -804,8 +810,8 @@ void ControlBarScheme::drawBackground( Coord2D multi, ICoord2D offset ) // draw it TheDisplay->drawImage(schemeImage->m_image, schemeImage->m_position.x * multi.x + offset.x, schemeImage->m_position.y * multi.y + offset.y, - (schemeImage->m_position.x + schemeImage->m_size.x) * multi.x + offset.x, - (schemeImage->m_position.y + schemeImage->m_size.y) * multi.y + offset.y); + schemeEdge(schemeImage->m_position.x + schemeImage->m_size.x, m_ScreenCreationRes.x, multi.x, offset.x), + schemeEdge(schemeImage->m_position.y + schemeImage->m_size.y, m_ScreenCreationRes.y, multi.y, offset.y)); it ++; } diff --git a/Core/GameEngine/Source/GameClient/GUI/GameWindow.cpp b/Core/GameEngine/Source/GameClient/GUI/GameWindow.cpp index 84dc88726cc..4bf525e1be2 100644 --- a/Core/GameEngine/Source/GameClient/GUI/GameWindow.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/GameWindow.cpp @@ -1432,6 +1432,26 @@ GameWindow *GameWindow::winPointInChild( Int x, Int y, Bool ignoreEnableCheck, B GameWindow *child; ICoord2D origin; + // the list of an open combo box reaches past the rects of its parents, so it is tested first or clicks on it would fall through + GameWindow *open = TheWindowManager ? TheWindowManager->winGetLoneWindow() : nullptr; + if( open && open != this && BitIsSet( open->winGetStyle(), GWS_COMBO_BOX ) && winIsChild( open ) ) + { + GameWindow *list = GadgetComboBoxGetListBox( open ); + Bool shown = list && !list->winIsHidden(); + for( parent = open; parent && shown; parent = parent->m_parent ) + { + shown = !BitIsSet( parent->m_status, WIN_STATUS_HIDDEN ); + } + ICoord2D position; + ICoord2D size; + open->winGetScreenPosition( &position.x, &position.y ); + open->winGetSize( &size.x, &size.y ); + if( shown && x >= position.x && x <= position.x + size.x && y >= position.y && y <= position.y + size.y ) + { + return open->winPointInChild( x, y, ignoreEnableCheck, playDisabledSound ); + } + } + for( child = m_child; child; child = child->m_next ) { diff --git a/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index e4b26f600ed..a0f1c1d04db 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -473,9 +473,10 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage* } // TheSuperHackers @fix Mauller 16/06/2025 fix RMB scrolling to allow it to scale with the user adjusted scroll factor + // TheSuperHackers @fix The drag distance is measured at 800x600, so the scroll speed does not depend on the screen resolution. Coord2D vec; - vec.x = (m_currentPos.x - m_anchor.x); - vec.y = (m_currentPos.y - m_anchor.y); + vec.x = (m_currentPos.x - m_anchor.x) * (800.0f / TheDisplay->getWidth()); + vec.y = (m_currentPos.y - m_anchor.y) * (600.0f / TheDisplay->getHeight()); // TheSuperHackers @info calculate the length of the vector to obtain the movement speed before the vector is normalized float vecLength = vec.length(); vec.normalize(); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DTextEntry.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DTextEntry.cpp index cf11ddebf9a..ce1a201f892 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DTextEntry.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DTextEntry.cpp @@ -295,10 +295,8 @@ void W3DGadgetTextEntryDraw( GameWindow *window, WinInstanceData *instData ) width = size.x - (2 * startOffset); start.x = origin.x + startOffset; // offset a little bit into the entry - if( BitIsSet( window->winGetStatus(), WIN_STATUS_ONE_LINE ) ) - start.y = size.y / 2 - fontHeight / 2; - else - start.y = origin.y + startOffset; // offset a little bit into the entry + // TheSuperHackers @bugfix arcticdolphin 23/10/2026 Center the text vertically in the entry + start.y = origin.y + (size.y - fontHeight) / 2; // draw the edit text drawTextEntryText( window, instData, textColor, textBorder, compositeColor, compositeBorder, @@ -449,10 +447,8 @@ void W3DGadgetTextEntryImageDraw( GameWindow *window, WinInstanceData *instData width = size.x - (2 * startOffset); start.x = origin.x + startOffset; // offset a little bit into the entry - if( BitIsSet( window->winGetStatus(), WIN_STATUS_ONE_LINE ) ) - start.y = size.y / 2 - fontHeight / 2; - else - start.y = origin.y + startOffset; // offset a little bit into the entry + // TheSuperHackers @bugfix arcticdolphin 23/10/2026 Center the text vertically in the entry + start.y = origin.y + (size.y - fontHeight) / 2; // draw the edit text drawTextEntryText( window, instData, textColor, textBorder, compositeColor, compositeBorder, diff --git a/Generals/Code/GameEngine/Include/GameClient/GameWindowManager.h b/Generals/Code/GameEngine/Include/GameClient/GameWindowManager.h index 4ce12aa8964..e03e5a0b532 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameWindowManager.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameWindowManager.h @@ -251,6 +251,7 @@ friend class GameWindow; virtual void winSetGrabWindow( GameWindow *window ); ///< set the grab window virtual GameWindow *winGetGrabWindow(); ///< who is currently 'held' by mouse virtual void winSetLoneWindow( GameWindow *window ); ///< set the open window + GameWindow *winGetLoneWindow() const { return m_loneWindow; } ///< the combo box whose list is open, if any virtual Bool isEnabled( GameWindow *win ); ///< is window or parents enabled virtual Bool isHidden( GameWindow *win ); ///< is parent or parents hidden diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp index e234b654278..3a5fcf81cae 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp @@ -528,8 +528,11 @@ static Bool parseScreenRect( const char *token, char *buffer, Real yScale = (Real)TheDisplay->getHeight() / (Real)createRes.y; screenRegion.lo.x = (Int)((Real)screenRegion.lo.x * xScale); screenRegion.lo.y = (Int)((Real)screenRegion.lo.y * yScale); - screenRegion.hi.x = (Int)((Real)screenRegion.hi.x * xScale); - screenRegion.hi.y = (Int)((Real)screenRegion.hi.y * yScale); + // an edge on the last pixel of the creation resolution reaches the screen edge at any resolution + const Int hiX = screenRegion.hi.x == createRes.x - 1 ? createRes.x : screenRegion.hi.x; + const Int hiY = screenRegion.hi.y == createRes.y - 1 ? createRes.y : screenRegion.hi.y; + screenRegion.hi.x = (Int)((Real)hiX * xScale); + screenRegion.hi.y = (Int)((Real)hiY * yScale); // // given the screen region upper left compute the upper left that we diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameWindow.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameWindow.cpp index 1fea607f890..4c347906911 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameWindow.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameWindow.cpp @@ -368,13 +368,24 @@ void W3DGameWinDefaultDraw( GameWindow *window, WinInstanceData *instData ) origin.x, origin.y, origin.x + size.x, origin.y + size.y ); + // The fill leaves room for the border. At other resolutions than the creation one it only does that for a border that is drawn, + // so a window without a visible border reaches its own edges. + Real fillInset = borderWidth; + if( TheDisplay != nullptr && ( TheDisplay->getWidth() != 800 || TheDisplay->getHeight() != 600 ) ) + { + Real red, green, blue, alpha; + GameGetColorComponentsReal( borderColor, &red, &green, &blue, &alpha ); + if( borderColor == WIN_COLOR_UNDEFINED || alpha <= 0.0f ) + fillInset = 0.0f; + } + // draw filled background if( color != WIN_COLOR_UNDEFINED ) TheWindowManager->winFillRect( color, borderWidth, - origin.x + borderWidth, - origin.y + borderWidth, - origin.x + size.x - borderWidth, - origin.y + size.y - borderWidth ); + origin.x + fillInset, + origin.y + fillInset, + origin.x + size.x - fillInset, + origin.y + size.y - fillInset ); } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 38595af3fcf..e2a2a6ef5e7 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -2681,6 +2681,23 @@ void W3DDisplay::drawImage( const Image *image, Int startX, Int startY, RectClass screen_rect(startX,startY,endX,endY); RectClass uv_rect(uv->lo.x,uv->lo.y,uv->hi.x,uv->hi.y); + // A stretched image samples half a texel inside its own rectangle, so bilinear filtering does not pull in the neighbours of an atlas texture. + // An image drawn at its own size is left as is. + { + const Bool rotated = BitIsSet( image->getStatus(), IMAGE_STATUS_ROTATED_90_CLOCKWISE ); + const Int imageWidth = image->getImageWidth(); + const Int imageHeight = image->getImageHeight(); + if (imageWidth > 1 && imageHeight > 1 && (endX - startX != imageWidth || endY - startY != imageHeight)) + { + const Real insetU = 0.5f * uv_rect.Width() / (Real)(rotated ? imageHeight : imageWidth); + const Real insetV = 0.5f * uv_rect.Height() / (Real)(rotated ? imageWidth : imageHeight); + uv_rect.Left += insetU; + uv_rect.Right -= insetU; + uv_rect.Top += insetV; + uv_rect.Bottom -= insetV; + } + } + if (m_isClippedEnabled) { //need to clip this quad to clip rectangle if (screen_rect.Left < m_clipRegion.lo.x || screen_rect.Right > m_clipRegion.hi.x || screen_rect.Top < m_clipRegion.lo.y || screen_rect.Bottom > m_clipRegion.hi.y) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h index 8d0bfb6686d..369b2f3697f 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h @@ -251,6 +251,7 @@ class GameWindowManager : public SubsystemInterface virtual void winSetGrabWindow( GameWindow *window ); ///< set the grab window virtual GameWindow *winGetGrabWindow(); ///< who is currently 'held' by mouse virtual void winSetLoneWindow( GameWindow *window ); ///< set the open window + GameWindow *winGetLoneWindow() const { return m_loneWindow; } ///< the combo box whose list is open, if any // Finds the top-level window at the mouse position that matches the required and forbidden status masks. virtual GameWindow* findWindowUnderMouse(GameWindow*& toolTipWindow, const ICoord2D* mousePos, unsigned int requiredStatusMask, unsigned int forbiddenStatusMask); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp index d588e90cc31..2f49e3d3dde 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp @@ -529,8 +529,11 @@ static Bool parseScreenRect( const char *token, char *buffer, Real yScale = (Real)TheDisplay->getHeight() / (Real)createRes.y; screenRegion.lo.x = (Int)((Real)screenRegion.lo.x * xScale); screenRegion.lo.y = (Int)((Real)screenRegion.lo.y * yScale); - screenRegion.hi.x = (Int)((Real)screenRegion.hi.x * xScale); - screenRegion.hi.y = (Int)((Real)screenRegion.hi.y * yScale); + // an edge on the last pixel of the creation resolution reaches the screen edge at any resolution + const Int hiX = screenRegion.hi.x == createRes.x - 1 ? createRes.x : screenRegion.hi.x; + const Int hiY = screenRegion.hi.y == createRes.y - 1 ? createRes.y : screenRegion.hi.y; + screenRegion.hi.x = (Int)((Real)hiX * xScale); + screenRegion.hi.y = (Int)((Real)hiY * yScale); // // given the screen region upper left compute the upper left that we diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameWindow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameWindow.cpp index 4a179a6299e..5ea62930e89 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameWindow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameWindow.cpp @@ -368,13 +368,24 @@ void W3DGameWinDefaultDraw( GameWindow *window, WinInstanceData *instData ) origin.x, origin.y, origin.x + size.x, origin.y + size.y ); + // The fill leaves room for the border. At other resolutions than the creation one it only does that for a border that is drawn, + // so a window without a visible border reaches its own edges. + Real fillInset = borderWidth; + if( TheDisplay != nullptr && ( TheDisplay->getWidth() != 800 || TheDisplay->getHeight() != 600 ) ) + { + Real red, green, blue, alpha; + GameGetColorComponentsReal( borderColor, &red, &green, &blue, &alpha ); + if( borderColor == WIN_COLOR_UNDEFINED || alpha <= 0.0f ) + fillInset = 0.0f; + } + // draw filled background if( color != WIN_COLOR_UNDEFINED ) TheWindowManager->winFillRect( color, borderWidth, - origin.x + borderWidth, - origin.y + borderWidth, - origin.x + size.x - borderWidth, - origin.y + size.y - borderWidth ); + origin.x + fillInset, + origin.y + fillInset, + origin.x + size.x - fillInset, + origin.y + size.y - fillInset ); } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 5eda9635d85..75c56463044 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -2821,6 +2821,23 @@ void W3DDisplay::drawImage( const Image *image, Int startX, Int startY, RectClass screen_rect(startX,startY,endX,endY); RectClass uv_rect(uv->lo.x,uv->lo.y,uv->hi.x,uv->hi.y); + // A stretched image samples half a texel inside its own rectangle, so bilinear filtering does not pull in the neighbours of an atlas texture. + // An image drawn at its own size is left as is. + { + const Bool rotated = BitIsSet( image->getStatus(), IMAGE_STATUS_ROTATED_90_CLOCKWISE ); + const Int imageWidth = image->getImageWidth(); + const Int imageHeight = image->getImageHeight(); + if (imageWidth > 1 && imageHeight > 1 && (endX - startX != imageWidth || endY - startY != imageHeight)) + { + const Real insetU = 0.5f * uv_rect.Width() / (Real)(rotated ? imageHeight : imageWidth); + const Real insetV = 0.5f * uv_rect.Height() / (Real)(rotated ? imageWidth : imageHeight); + uv_rect.Left += insetU; + uv_rect.Right -= insetU; + uv_rect.Top += insetV; + uv_rect.Bottom -= insetV; + } + } + if (m_isClippedEnabled) { //need to clip this quad to clip rectangle if (screen_rect.Left < m_clipRegion.lo.x || screen_rect.Right > m_clipRegion.hi.x || screen_rect.Top < m_clipRegion.lo.y || screen_rect.Bottom > m_clipRegion.hi.y) diff --git a/GeneralsOnline/GameData/Art/Textures/GeneralsOnline_CheckboxSliderArt.tga b/GeneralsOnline/GameData/Art/Textures/GeneralsOnline_CheckboxSliderArt.tga new file mode 100644 index 00000000000..4773a7adbed Binary files /dev/null and b/GeneralsOnline/GameData/Art/Textures/GeneralsOnline_CheckboxSliderArt.tga differ diff --git a/GeneralsOnline/GameData/Data/INI/MappedImages/TextureSize_512/ZZ_GeneralsOnline_CheckboxSliderArt.INI b/GeneralsOnline/GameData/Data/INI/MappedImages/TextureSize_512/ZZ_GeneralsOnline_CheckboxSliderArt.INI new file mode 100644 index 00000000000..3ce20429f4b --- /dev/null +++ b/GeneralsOnline/GameData/Data/INI/MappedImages/TextureSize_512/ZZ_GeneralsOnline_CheckboxSliderArt.INI @@ -0,0 +1,47 @@ +MappedImage Active-Unchecked + Texture = SCSmShellUserInterface512_001.tga + TextureWidth = 512 + TextureHeight = 512 + Coords = Left:0 Top:425 Right:22 Bottom:447 + Status = NONE +End + +MappedImage Active-Checked + Texture = SCSmShellUserInterface512_001.tga + TextureWidth = 512 + TextureHeight = 512 + Coords = Left:278 Top:426 Right:300 Bottom:448 + Status = NONE +End + +MappedImage Active-HiLighted + Texture = SCSmShellUserInterface512_001.tga + TextureWidth = 512 + TextureHeight = 512 + Coords = Left:24 Top:425 Right:46 Bottom:447 + Status = NONE +End + +MappedImage Disabled-Unchecked + Texture = SCSmShellUserInterface512_001.tga + TextureWidth = 512 + TextureHeight = 512 + Coords = Left:188 Top:423 Right:210 Bottom:445 + Status = NONE +End + +MappedImage Disabled-Checked + Texture = SCSmShellUserInterface512_001.tga + TextureWidth = 512 + TextureHeight = 512 + Coords = Left:302 Top:426 Right:324 Bottom:448 + Status = NONE +End + +MappedImage hilightedbox + Texture = GeneralsOnline_CheckboxSliderArt.tga + TextureWidth = 16 + TextureHeight = 16 + Coords = Left:0 Top:0 Right:11 Bottom:13 + Status = NONE +End