From 06ec2e6fb6a018ca21be686010c969d906541457 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Tue, 6 Jan 2026 18:14:56 +0300 Subject: [PATCH 1/9] Implement row animation for text entries --- .../Source/GameNetwork/GameSpy/LobbyUtils.cpp | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp index 95935c06d75..90026d74177 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp @@ -95,6 +95,74 @@ enum { }; #endif +// --------------------------------------------------------------------------- +// Game list row animation +// --------------------------------------------------------------------------- +struct GameRowAnim +{ + float currentIndex; + float targetIndex; + bool alive; + + GameRowAnim() + : currentIndex(0.0f) + , targetIndex(0.0f) + , alive(false) + { + } +}; + +static std::map g_gameListAnim; + +// Update or initialize the smoothed index for this lobbyID +static float UpdateAndGetGameRowIndex(int lobbyID, float logicalIndex) +{ + GameRowAnim& anim = g_gameListAnim[lobbyID]; + + // First time we see this ID, snap current to logical + if (!anim.alive) + { + anim.currentIndex = logicalIndex; + anim.targetIndex = logicalIndex; + anim.alive = true; + } + + // Move target to the new logical index + anim.targetIndex = logicalIndex; + + const float t = 0.25f; // smoothing factor + + float delta = anim.targetIndex - anim.currentIndex; + anim.currentIndex += delta * t; + + // If close enough, snap + if (std::fabs(anim.currentIndex - anim.targetIndex) < 0.01f) + { + anim.currentIndex = anim.targetIndex; + } + + return anim.currentIndex; +} + +// Clears animation state for IDs that no longer exist +static void CleanupMissingGameRows(const std::vector& lobbies) +{ + std::map stillPresent; + + for (const LobbyEntry& lobby : lobbies) + { + stillPresent[lobby.lobbyID] = true; + } + + for (auto it = g_gameListAnim.begin(); it != g_gameListAnim.end(); ) + { + if (stillPresent.find(it->first) == stillPresent.end()) + it = g_gameListAnim.erase(it); + else + ++it; + } +} + static NameKeyType buttonSortAlphaID = NAMEKEY_INVALID; static NameKeyType buttonSortPingID = NAMEKEY_INVALID; static NameKeyType buttonSortBuddiesID = NAMEKEY_INVALID; @@ -1158,6 +1226,12 @@ void RefreshGameListBox(GameWindow* win, Bool showMap) { LobbyEntry lobby = *sglIt; + // Logical index for this entry in the new sorted list + float logicalIndex = (float)i; + + // register/update animation index for this lobbyID + UpdateAndGetGameRowIndex(lobby.lobbyID, logicalIndex); + Int index = insertGame(win, lobby, showMap); if (lobby.lobbyID == selectedID) { @@ -1296,6 +1370,30 @@ void RefreshGameInfoListBox( GameWindow *mainWin, GameWindow *win ) } +// --------------------------------------------------------------------------- +// compute pixel offset for a game list row +// --------------------------------------------------------------------------- +int GetGameListRowPixelOffsetForRow(GameWindow* window, int rowIndex, int rowHeight) +{ + if (!window) + return 0; + + // We rely on listbox item data storing lobbyID, like RefreshGameListBox uses + Int lobbyID = (Int)GadgetListBoxGetItemData(window, rowIndex); + if (lobbyID == 0) + return 0; + + // Get smoothed "visual index" for this lobbyID + float visualIndex = UpdateAndGetGameRowIndex(lobbyID, (float)rowIndex); + + // Offset = (visualIndex - logicalIndex) * rowHeight + float offsetRows = visualIndex - (float)rowIndex; + int pixelOffset = (int)(offsetRows * (float)rowHeight); + + return pixelOffset; +} + + void RefreshGameListBoxes( void ) { GameWindow *main = GetGameListBox(); From 5e3ba0d323b81917f5d63c390391c649df794826 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Tue, 6 Jan 2026 18:22:41 +0300 Subject: [PATCH 2/9] Adjust drawing positions --- .../GameClient/GUI/Gadget/W3DListBox.cpp | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DListBox.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DListBox.cpp index ff3ee8a701c..a4a30637ec8 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DListBox.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DListBox.cpp @@ -56,6 +56,8 @@ // DEFINES //////////////////////////////////////////////////////////////////// +int GetGameListRowPixelOffsetForRow(GameWindow* window, int rowIndex, int rowHeight); + // PRIVATE TYPES ////////////////////////////////////////////////////////////// // PRIVATE DATA /////////////////////////////////////////////////////////////// @@ -195,6 +197,14 @@ static void drawListBoxText( GameWindow *window, WinInstanceData *instData, IRegion2D clipRegion; ICoord2D start, end; + Color debugBg = TheWindowManager->winMakeColor(3, 93, 166, 20); + TheWindowManager->winFillRect( + debugBg, + WIN_DRAW_LINE_WIDTH, + x, y, + x + width, y + height + ); + // // save the clipping information region cause we're going to use it here // in drawing the text @@ -233,7 +243,11 @@ static void drawListBoxText( GameWindow *window, WinInstanceData *instData, //textColor = list->listData[i].textColor; selected = FALSE; - if( list->multiSelect ) + int rowDrawY = drawY; + + rowDrawY += GetGameListRowPixelOffsetForRow(window, i, listLineHeight); + + if (list->multiSelect) { Int j = 0; @@ -297,7 +311,7 @@ static void drawListBoxText( GameWindow *window, WinInstanceData *instData, // region of the edge of the listbox // start.x = x; - start.y = drawY; + start.y = rowDrawY; end.x = start.x + width; end.y = start.y + listLineHeight; @@ -338,7 +352,7 @@ static void drawListBoxText( GameWindow *window, WinInstanceData *instData, // region of the edge of the listbox // start.x = x; - start.y = drawY; + start.y = rowDrawY; end.x = start.x + width; end.y = start.y + listLineHeight; @@ -360,7 +374,7 @@ static void drawListBoxText( GameWindow *window, WinInstanceData *instData, // region of the edge of the listbox // start.x = x + 1; - start.y = drawY + 1; + start.y = rowDrawY + 1; end.x = start.x + width - 2; end.y = start.y + listLineHeight - 2; @@ -422,7 +436,7 @@ static void drawListBoxText( GameWindow *window, WinInstanceData *instData, // draw this text after setting the clip region for it string->setClipRegion( &columnRegion ); string->draw( columnX + TEXT_X_OFFSET, - drawY, + rowDrawY, textColor, dropColor ); @@ -461,9 +475,9 @@ static void drawListBoxText( GameWindow *window, WinInstanceData *instData, else offsetX = columnX; if(height < list->listData[i].height) - offsetY = drawY + ((list->listData[i].height - height) / 2); + offsetY = rowDrawY + ((list->listData[i].height - height) / 2); else - offsetY = drawY; + offsetY = rowDrawY; offsetY++; if(offsetX Date: Tue, 6 Jan 2026 23:16:16 +0300 Subject: [PATCH 3/9] tweak --- .../Source/GameNetwork/GameSpy/LobbyUtils.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp index 90026d74177..24211d94940 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp @@ -98,19 +98,6 @@ enum { // --------------------------------------------------------------------------- // Game list row animation // --------------------------------------------------------------------------- -struct GameRowAnim -{ - float currentIndex; - float targetIndex; - bool alive; - - GameRowAnim() - : currentIndex(0.0f) - , targetIndex(0.0f) - , alive(false) - { - } -}; static std::map g_gameListAnim; From e884f5c0024cec457e896837f85d764a4f9d899b Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Tue, 6 Jan 2026 23:17:16 +0300 Subject: [PATCH 4/9] Add GameRowAnim struct --- .../Include/GameClient/GadgetTextEntry.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GadgetTextEntry.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GadgetTextEntry.h index dde3a09bec7..8780ed3f27a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GadgetTextEntry.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GadgetTextEntry.h @@ -121,3 +121,17 @@ inline Color GadgetTextEntryGetHiliteColor( GameWindow *g ) { return g->w inline Color GadgetTextEntryGetHiliteBorderColor( GameWindow *g ) { return g->winGetHiliteBorderColor( 0 ); } // EXTERNALS ////////////////////////////////////////////////////////////////// + +struct GameRowAnim +{ + float currentIndex; + float targetIndex; + bool alive; + + GameRowAnim() + : currentIndex(0.0f) + , targetIndex(0.0f) + , alive(false) + { + } +}; From b0435366d7b65373b50efcff227f6e0d87474e29 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Wed, 7 Jan 2026 01:56:20 +0300 Subject: [PATCH 5/9] Improve row animation --- .../Source/GameNetwork/GameSpy/LobbyUtils.cpp | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp index 24211d94940..839ab7dd152 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp @@ -96,39 +96,35 @@ enum { #endif // --------------------------------------------------------------------------- -// Game list row animation +// row entry animation // --------------------------------------------------------------------------- static std::map g_gameListAnim; -// Update or initialize the smoothed index for this lobbyID +// initialize the smoothed index for this lobbyID static float UpdateAndGetGameRowIndex(int lobbyID, float logicalIndex) { - GameRowAnim& anim = g_gameListAnim[lobbyID]; + GameRowAnim& anim = g_gameRowAnim[lobbyID]; - // First time we see this ID, snap current to logical - if (!anim.alive) - { - anim.currentIndex = logicalIndex; - anim.targetIndex = logicalIndex; - anim.alive = true; - } - - // Move target to the new logical index - anim.targetIndex = logicalIndex; - - const float t = 0.25f; // smoothing factor + if (!anim.alive) + { + // start slightly below, then glide into place + anim.currentIndex = logicalIndex + 1.5f; + anim.targetIndex = logicalIndex; + anim.alive = true; + } + else + { + anim.targetIndex = logicalIndex; + } - float delta = anim.targetIndex - anim.currentIndex; - anim.currentIndex += delta * t; + // how fast to snap into place + const float t = 0.1f; - // If close enough, snap - if (std::fabs(anim.currentIndex - anim.targetIndex) < 0.01f) - { - anim.currentIndex = anim.targetIndex; - } + float delta = anim.targetIndex - anim.currentIndex; + anim.currentIndex += delta * t; - return anim.currentIndex; + return anim.currentIndex; } // Clears animation state for IDs that no longer exist From 0e320d3986563fdc6b9c73b8bd148b2cb4c4d9e7 Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Wed, 7 Jan 2026 01:59:04 +0300 Subject: [PATCH 6/9] Fix reference --- Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp index 839ab7dd152..210b472c0b3 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp @@ -104,7 +104,7 @@ static std::map g_gameListAnim; // initialize the smoothed index for this lobbyID static float UpdateAndGetGameRowIndex(int lobbyID, float logicalIndex) { - GameRowAnim& anim = g_gameRowAnim[lobbyID]; + GameRowAnim& anim = g_gameListAnim[lobbyID]; if (!anim.alive) { From 8b841633e0b570c01b9512d17a5f00c252e1384a Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Wed, 7 Jan 2026 02:07:38 +0300 Subject: [PATCH 7/9] drawY to rowDrawY for list box rendering --- .../Source/W3DDevice/GameClient/GUI/Gadget/W3DListBox.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DListBox.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DListBox.cpp index a4a30637ec8..2025169cc29 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DListBox.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DListBox.cpp @@ -410,12 +410,12 @@ static void drawListBoxText( GameWindow *window, WinInstanceData *instData, // setup the Clip Region size columnRegion.lo.x = columnX; - columnRegion.lo.y = drawY; + columnRegion.lo.y = rowDrawY; if(list->columns == 1 && list->slider && list->slider->winIsHidden()) columnRegion.hi.x = columnX + width-3; else columnRegion.hi.x = columnX + list->columnWidth[j]; - columnRegion.hi.y = drawY + list->listData[i].height; + columnRegion.hi.y = rowDrawY + list->listData[i].height; if(columnRegion.lo.y < clipRegion.lo.y ) columnRegion.lo.y = clipRegion.lo.y; if( columnRegion.hi.y > clipRegion.hi.y ) @@ -451,7 +451,7 @@ static void drawListBoxText( GameWindow *window, WinInstanceData *instData, // set clip region and draw string->setClipRegion( &columnRegion ); string->draw( columnX + TEXT_X_OFFSET, - drawY, + rowDrawY, textColor, dropColor ); } From 501cff7d61f103aeb3d7b95b71f4df917d5ceece Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Wed, 7 Jan 2026 04:25:30 +0300 Subject: [PATCH 8/9] Adjust animation index calculation to ensure no overlapping --- Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp index 210b472c0b3..4930fb1c5dc 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp @@ -109,7 +109,7 @@ static float UpdateAndGetGameRowIndex(int lobbyID, float logicalIndex) if (!anim.alive) { // start slightly below, then glide into place - anim.currentIndex = logicalIndex + 1.5f; + anim.currentIndex = logicalIndex + 0.3f; anim.targetIndex = logicalIndex; anim.alive = true; } @@ -1366,8 +1366,10 @@ int GetGameListRowPixelOffsetForRow(GameWindow* window, int rowIndex, int rowHei if (lobbyID == 0) return 0; + int animKey = lobbyID * 1024 + rowIndex; + // Get smoothed "visual index" for this lobbyID - float visualIndex = UpdateAndGetGameRowIndex(lobbyID, (float)rowIndex); + float visualIndex = UpdateAndGetGameRowIndex(animKey, (float)rowIndex); // Offset = (visualIndex - logicalIndex) * rowHeight float offsetRows = visualIndex - (float)rowIndex; From 82e32fa46658c54cae0862d467fb0f45b2facdba Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Wed, 7 Jan 2026 05:28:54 +0300 Subject: [PATCH 9/9] keep using lobbyID for game list Refactor animation key calculation for lobby lists. --- .../Source/GameNetwork/GameSpy/LobbyUtils.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp index 4930fb1c5dc..f8a3c84986c 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp @@ -1366,7 +1366,20 @@ int GetGameListRowPixelOffsetForRow(GameWindow* window, int rowIndex, int rowHei if (lobbyID == 0) return 0; - int animKey = lobbyID * 1024 + rowIndex; + GameWindow* mainGameList = GetGameListBox(); + + int animKey; + if (window == mainGameList) + { + // For the main game list: use lobbyID + animKey = lobbyID; + } + else + { + // For all other lists: keep per row key to avoid overlap + animKey = lobbyID * 1024 + rowIndex; + } + // Get smoothed "visual index" for this lobbyID float visualIndex = UpdateAndGetGameRowIndex(animKey, (float)rowIndex);