diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp index 95935c06d75..f8a3c84986c 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp @@ -95,6 +95,57 @@ enum { }; #endif +// --------------------------------------------------------------------------- +// row entry animation +// --------------------------------------------------------------------------- + +static std::map g_gameListAnim; + +// initialize the smoothed index for this lobbyID +static float UpdateAndGetGameRowIndex(int lobbyID, float logicalIndex) +{ + GameRowAnim& anim = g_gameListAnim[lobbyID]; + + if (!anim.alive) + { + // start slightly below, then glide into place + anim.currentIndex = logicalIndex + 0.3f; + anim.targetIndex = logicalIndex; + anim.alive = true; + } + else + { + anim.targetIndex = logicalIndex; + } + + // how fast to snap into place + const float t = 0.1f; + + float delta = anim.targetIndex - anim.currentIndex; + anim.currentIndex += delta * t; + + 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 +1209,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 +1353,45 @@ 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; + + 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); + + // Offset = (visualIndex - logicalIndex) * rowHeight + float offsetRows = visualIndex - (float)rowIndex; + int pixelOffset = (int)(offsetRows * (float)rowHeight); + + return pixelOffset; +} + + void RefreshGameListBoxes( void ) { GameWindow *main = GetGameListBox(); 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) + { + } +}; 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..2025169cc29 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; @@ -396,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 ) @@ -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 ); @@ -437,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 ); } @@ -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