//========= Copyright Valve Corporation, All rights reserved. ============// // //=======================================================================================// #include "cbase.h" #if defined( REPLAY_ENABLED ) #include "replaybrowserlistitempanel.h" #include "replaybrowsermainpanel.h" #include "replaybrowserlistpanel.h" #include "replaybrowserrenderdialog.h" #include "replaybrowsermovieplayerpanel.h" #include "vgui/IInput.h" #include "vgui/IVGui.h" #include "filesystem.h" #include "replay/screenshot.h" #include "replay/ireplaymanager.h" #include "confirm_dialog.h" #include "replay/ireplaymoviemanager.h" #include "econ/econ_controls.h" //----------------------------------------------------------------------------- using namespace vgui; extern IReplayMovieManager *g_pReplayMovieManager; //----------------------------------------------------------------------------- #define REPLAY_BORDER_WIDTH XRES(2) #define REPLAY_BORDER_HEIGHT YRES(2) #define REPLAY_BUFFER_HEIGHT XRES(5) //----------------------------------------------------------------------------- CReplayScreenshotSlideshowPanel::CReplayScreenshotSlideshowPanel( Panel *pParent, const char *pName, ReplayHandle_t hReplay ) : CSlideshowPanel( pParent, pName ), m_hReplay( hReplay ) { CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( hReplay ); // Add all screenshots for the given replay char szImage[ MAX_OSPATH ]; for ( int i = 0; i < pReplay->GetScreenshotCount(); ++i ) { const CReplayScreenshot *pScreenshot = pReplay->GetScreenshot( i ); V_snprintf( szImage, sizeof( szImage ), "replay/thumbnails/%s.vtf", pScreenshot->m_szBaseFilename ); // Add image to list of slideshow images AddImage( szImage ); if ( i == 0 ) { // Set first image GetImagePanel()->SetImage( szImage ); } } } void CReplayScreenshotSlideshowPanel::PerformLayout() { BaseClass::PerformLayout(); } //----------------------------------------------------------------------------- CReplayBrowserThumbnail::CReplayBrowserThumbnail( Panel *pParent, const char *pName, QueryableReplayItemHandle_t hReplayItem, IReplayItemManager *pReplayItemManager ) : CReplayBasePanel( pParent, pName ), m_hReplayItem( hReplayItem ), m_pReplayItemManager( pReplayItemManager ), m_bMouseOver( false ), m_pMoviePlayer( NULL ), m_flLastMovieScrubTime( 0.0f ), m_flHoverStartTime( 0.0f ), m_flLastProgressChangeTime( 0.0f ) { SetScheme( "ClientScheme" ); ivgui()->AddTickSignal( GetVPanel(), 10 ); // Add the list panel as an action signal target // NOTE: Vile hack. Panel *pTarget = GetParent(); while ( pTarget ) { if ( !V_strcmp( "BasePage", pTarget->GetName() ) ) break; pTarget = pTarget->GetParent(); } Assert( pTarget ); AddActionSignalTarget( pTarget ); m_pScreenshotThumb = new CCrossfadableImagePanel( this, "ScreenshotThumbnail" ); m_pTitle = new Label( this, "TitleLabel", "" ); m_pDownloadLabel = new Label( this, "DownloadLabel", "" ); m_pRecordingInProgressLabel = new Label( this, "RecordingInProgressLabel", "" ); m_pDownloadProgress = new ProgressBar( this, "DownloadProgress" ); m_pDownloadButton = new CExButton( this, "DownloadButton", "#Replay_Download" ); m_pDeleteButton = new CExButton( this, "DeleteButton", "#X_Delete" ); m_pErrorLabel = new Label( this, "ErrorLabel", "" ); m_pDownloadOverlay = new Panel( this, "DownloadOverlay" ); m_pBorderPanel = new EditablePanel( this, "BorderPanel" ); m_pScreenshotThumb->InstallMouseHandler( this ); m_pDownloadButton->AddActionSignalTarget( this ); m_pDownloadButton->SetCommand( new KeyValues( "download" ) ); m_pDeleteButton->AddActionSignalTarget( this ); m_pDeleteButton->SetCommand( new KeyValues( "delete_replayitem" ) ); SetProportional( true ); SetReplayItem( hReplayItem ); } CReplayBrowserThumbnail::~CReplayBrowserThumbnail() { // Clear the download event handler ptr CGenericClassBasedReplay *pReplay = GetReplay(); if ( pReplay ) { pReplay->m_pDownloadEventHandler = NULL; } SetupReplayItemUserData( NULL ); ivgui()->RemoveTickSignal( GetVPanel() ); } void CReplayBrowserThumbnail::SetReplayItem( QueryableReplayItemHandle_t hReplayItem ) { if ( m_hReplayItem != REPLAY_HANDLE_INVALID && m_hReplayItem != hReplayItem ) { IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( m_hReplayItem ); if ( pReplayItem ) { pReplayItem->SetUserData( NULL ); CGenericClassBasedReplay *pTFReplay = ToGenericClassBasedReplay( pReplayItem->GetItemReplay() ); pTFReplay->m_pDownloadEventHandler = NULL; } } m_hReplayItem = hReplayItem; if ( hReplayItem != REPLAY_HANDLE_INVALID ) { // Set this as user data SetupReplayItemUserData( (void *)this ); } InvalidateLayout(); } void CReplayBrowserThumbnail::SetupReplayItemUserData( void *pUserData ) { IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( m_hReplayItem ); if ( pReplayItem ) { pReplayItem->SetUserData( pUserData ); } } void CReplayBrowserThumbnail::OnTick() { const CGenericClassBasedReplay *pReplay = GetReplay(); if ( !pReplay ) return; // Get out if the delete confirmation dialog is up if ( vgui::input()->GetAppModalSurface() ) return; // Need to update layout if status has changed, since some buttons may need to be shifted around // TODO: Only layout when necessary InvalidateLayout( true, false ); // Get mouse position and store state int x,y; vgui::input()->GetCursorPos(x, y); bool bOldMouseOver = m_bMouseOver; m_bMouseOver = m_pBorderPanel->IsWithin(x,y); // If we are just starting to hover over the thumbnail, mark the time if ( bOldMouseOver != m_bMouseOver && m_bMouseOver ) { m_flHoverStartTime = gpGlobals->realtime; } const char *pBorderName = m_bMouseOver ? "ReplayHighlightBorder" : "ReplayDefaultBorder"; IBorder *pBorder = scheme()->GetIScheme( GetScheme() )->GetBorder( pBorderName ); m_pBorderPanel->SetBorder( pBorder ); // Set visibility of buttons and such - a player may have saved their replay but not died yet, // in which case pReplay->m_bComplete will be false. const IQueryableReplayItem *pItem = m_pReplayItemManager->GetItem( m_hReplayItem ); bool bIncomplete = !pReplay->m_bComplete; bool bDownloadPhase = !pItem->IsItemAMovie() && pReplay->m_nStatus == CReplay::REPLAYSTATUS_DOWNLOADPHASE; bool bErrorState = pReplay->m_nStatus == CReplay::REPLAYSTATUS_ERROR; m_pDownloadButton->SetVisible( false ); m_pDeleteButton->SetVisible( bErrorState || bDownloadPhase ); m_pDownloadOverlay->SetVisible( bErrorState || bDownloadPhase || bIncomplete ); m_pDownloadProgress->SetVisible( bDownloadPhase ); m_pErrorLabel->SetVisible( bErrorState ); m_pRecordingInProgressLabel->SetVisible( bIncomplete ); UpdateProgress( bDownloadPhase, pReplay ); } void CReplayBrowserThumbnail::UpdateProgress( bool bDownloadPhase, const CReplay *pReplay ) { if ( !bDownloadPhase ) return; // Get current download progress const float flProgress = g_pClientReplayContext->GetReplayManager()->GetDownloadProgress( pReplay ); // Has progress changed? const int nNewProgress = 100 * flProgress; const int nOldProgress = 100 * m_pDownloadProgress->GetProgress(); const float flCurTime = gpGlobals->realtime; if ( nNewProgress != nOldProgress ) { m_flLastProgressChangeTime = flCurTime; } // Set download progress m_pDownloadProgress->SetProgress( flProgress ); const char *pToken = "#Replay_Waiting"; if ( ReplayUI_GetBrowserPanel() && ( flCurTime - ReplayUI_GetBrowserPanel()->GetTimeOpened() > 2.0f ) ) { // Use "downloading" string if progress has changed in the last two seconds if ( ( flCurTime - m_flLastProgressChangeTime ) < 2.0f ) { pToken = "#Replay_Downloading"; } } const wchar_t *pLocalizedText = g_pVGuiLocalize->Find( pToken ); if ( pLocalizedText ) { // Add animating '...' to end of string wchar_t wszText[128]; wcscpy( wszText, pLocalizedText ); int nNumPeriods = fmod( gpGlobals->realtime * 2.0f, 4.0f ); // Max of 3 dots const int nLen = wcslen( wszText ); wcscat( wszText, L"..." ); wszText[ nLen + nNumPeriods ] = L'\0'; m_pDownloadLabel->SetText( wszText ); m_pDownloadLabel->SizeToContents(); m_pDownloadLabel->SetWide( m_pDownloadProgress->GetWide() ); m_pDownloadLabel->SetPos( 3, (m_pDownloadProgress->GetTall() - m_pDownloadLabel->GetTall()) / 2 ); } } void CReplayBrowserThumbnail::ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( "resource/ui/replaybrowser/listthumbnail.res", "GAME" ); // Get default from the .res file m_clrDefaultBg = GetBgColor(); m_clrHighlight = GetSchemeColor( "TanDark", Color( 255, 255, 255, 255 ), pScheme ); } void CReplayBrowserThumbnail::PerformLayout() { BaseClass::PerformLayout(); const CGenericClassBasedReplay *pReplay = GetReplay(); AssertValidReadPtr( pReplay ); if ( !pReplay ) return; // Get thumbnail for first screenshot char szImage[MAX_OSPATH] = { '\0' }; bool bHasScreenshots = false; if ( pReplay->GetScreenshotCount() ) { // Use first screenshot for thumbnail bHasScreenshots = true; const CReplayScreenshot *pScreenshot = pReplay->GetScreenshot( 0 ); V_snprintf( szImage, sizeof( szImage ), "replay/thumbnails/%s.vtf", pScreenshot->m_szBaseFilename ); } // See if it exists char szSearch[MAX_OSPATH]; V_snprintf( szSearch, sizeof( szSearch ), "materials/vgui/%s", szImage ); bool bShowScreenshotThumb = true; if ( !bHasScreenshots || !g_pFullFileSystem || !g_pFullFileSystem->FileExists( szSearch, "GAME" ) ) { // Hide it bShowScreenshotThumb = false; } // Scale the screenshot so that it clips off the dead area of the power of 2 texture float flScale = pReplay->GetScreenshotCount() == 0 ? 1.0f : ( (float)m_pScreenshotThumb->GetWide() / ( .95f * pReplay->GetScreenshot( 0 )->m_nWidth ) ); m_pScreenshotThumb->SetScaleAmount( flScale ); m_pScreenshotThumb->SetShouldScaleImage( true ); if ( bShowScreenshotThumb ) { // We don't want to actually hide it (via SetVisible()), since we need to get mouse click events from the image panel m_pScreenshotThumb->SetImage( szImage ); } // Setup progress bar & label m_pDownloadProgress->SetWide( GetWide() * .95f ); m_pDownloadProgress->SetSegmentInfo( 0, 1 ); m_pDownloadProgress->SetBarInset( 2 ); m_pDownloadProgress->SetMargin( 2 ); m_pDownloadProgress->SetPos( (GetWide() - m_pDownloadProgress->GetWide()) / 2, (m_pScreenshotThumb->GetTall() - m_pDownloadProgress->GetTall()) / 2 ); m_pDownloadLabel->SetParent( m_pDownloadProgress ); m_pDownloadLabel->SetWide( m_pDownloadProgress->GetWide() ); m_pDownloadLabel->SetPos( 3, (m_pDownloadProgress->GetTall() - m_pDownloadLabel->GetTall()) / 2 ); m_pErrorLabel->SizeToContents(); m_pErrorLabel->SetPos( (GetWide() - m_pErrorLabel->GetWide()) / 2, (m_pScreenshotThumb->GetTall() - m_pErrorLabel->GetTall()) / 2 ); // Center the title control horizontally int nThumbX, nThumbY, nThumbW, nThumbH; UpdateTitleText(); m_pScreenshotThumb->GetBounds( nThumbX, nThumbY, nThumbW, nThumbH ); m_pDownloadOverlay->SetBounds( 0, 0, GetWide(), GetTall() ); if ( m_pMoviePlayer ) { m_pMoviePlayer->SetBounds( nThumbX, nThumbY, nThumbW, nThumbH ); } // Setup recording-in-progress m_pRecordingInProgressLabel->SetBounds( nThumbX, nThumbY, nThumbW, nThumbH ); m_pRecordingInProgressLabel->InvalidateLayout( true, false ); // Need this for centerwrap to work properly bool bDownloadPhase = pReplay->m_nStatus == CReplay::REPLAYSTATUS_DOWNLOADPHASE; int nButtonWidth = 9 * GetWide() / 10; int nButtonX = nThumbX + ( m_pScreenshotThumb->GetWide() - nButtonWidth ) / 2; int nDownloadButtonY, nDeleteButtonY; if ( bDownloadPhase ) { nDownloadButtonY = nThumbY + 12; nDeleteButtonY = nThumbY + m_pScreenshotThumb->GetTall() - m_pDeleteButton->GetTall() - 12; } else { nDownloadButtonY = 0; // We don't care about this now, since it will not be visible nDeleteButtonY = ( m_pScreenshotThumb->GetTall() - m_pDeleteButton->GetTall() ) / 2; } // Adjust download button position m_pDownloadButton->SetPos( nButtonX, nDownloadButtonY ); m_pDownloadButton->SetWide( nButtonWidth ); } void CReplayBrowserThumbnail::OnDownloadClicked( KeyValues *pParams ) { // Begin the download OnCommand( "download" ); } void CReplayBrowserThumbnail::OnDeleteReplay( KeyValues *pParams ) { OnCommand( "delete_replayitem" ); } void CReplayBrowserThumbnail::OnCommand( const char *pCommand ) { const CGenericClassBasedReplay *pReplay = GetReplay(); AssertValidReadPtr( pReplay ); if ( !pReplay ) return; if ( FStrEq( pCommand, "details" ) ) // Display replay details? { char szCmd[256]; V_snprintf( szCmd, sizeof( szCmd ), "details%i", (int)m_hReplayItem ); PostActionSignal( new KeyValues( "Command", "command", szCmd ) ); } else if ( FStrEq( pCommand, "delete_replayitem" ) ) // Delete the replay? { ReplayUI_GetBrowserPanel()->AttemptToDeleteReplayItem( this, m_hReplayItem, m_pReplayItemManager, -1 ); } else { BaseClass::OnCommand( pCommand ); } } void CReplayBrowserThumbnail::OnMousePressed( MouseCode code ) { if ( code == MOUSE_LEFT ) { OnCommand( "details" ); } } void CReplayBrowserThumbnail::UpdateTitleText() { IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( m_hReplayItem ); m_pTitle->SetText( pReplayItem->GetItemTitle() ); } CGenericClassBasedReplay *CReplayBrowserThumbnail::GetReplay() { IQueryableReplayItem *pItem = m_pReplayItemManager->GetItem( m_hReplayItem ); if ( !pItem ) return NULL; return ToGenericClassBasedReplay( pItem->GetItemReplay() ); } IQueryableReplayItem *CReplayBrowserThumbnail::GetReplayItem() { return m_pReplayItemManager->GetItem( m_hReplayItem ); } //----------------------------------------------------------------------------- CReplayBrowserThumbnailRow::CReplayBrowserThumbnailRow( Panel *pParent, const char *pName, IReplayItemManager *pReplayItemManager ) : BaseClass( pParent, pName ), m_pReplayItemManager( pReplayItemManager ) { SetProportional( true ); } void CReplayBrowserThumbnailRow::AddReplayThumbnail( const IQueryableReplayItem *pItem ) { CReplayBrowserThumbnail *pThumbnail = new CReplayBrowserThumbnail( this, "ListThumbnail", pItem->GetItemHandle(), m_pReplayItemManager ); m_vecThumbnails.AddToTail( pThumbnail ); } void CReplayBrowserThumbnailRow::AddReplayThumbnail( QueryableReplayItemHandle_t hReplayItem ) { CReplayBrowserThumbnail *pThumbnail = new CReplayBrowserThumbnail( this, "ListThumbnail", hReplayItem, m_pReplayItemManager ); m_vecThumbnails.AddToTail( pThumbnail ); } void CReplayBrowserThumbnailRow::DeleteReplayItemThumbnail( const IQueryableReplayItem *pReplayItem ) { CReplayBrowserThumbnail *pThumbnail = FindThumbnail( pReplayItem ); int nThumbnailElement = m_vecThumbnails.Find( pThumbnail ); if ( nThumbnailElement == m_vecThumbnails.InvalidIndex() ) { AssertMsg( 0, "REPLAY: Should have found replay thumbnail while attempting to delete it from the browser." ); return; } // Delete the actual panel ivgui()->RemoveTickSignal( pThumbnail->GetVPanel() ); pThumbnail->MarkForDeletion(); // Remove from list of thumbs m_vecThumbnails.Remove( nThumbnailElement ); } int CReplayBrowserThumbnailRow::GetNumVisibleReplayItems() const { int iCount = 0; FOR_EACH_VEC( m_vecThumbnails, i ) { CReplayBrowserThumbnail *pThumbnail = m_vecThumbnails[ i ]; if ( pThumbnail->IsVisible() ) { iCount++; } } return iCount; } CReplayBrowserThumbnail *CReplayBrowserThumbnailRow::FindThumbnail( const IQueryableReplayItem *pReplayItem ) { AssertValidReadPtr( pReplayItem ); FOR_EACH_VEC( m_vecThumbnails, i ) { CReplayBrowserThumbnail *pThumbnail = m_vecThumbnails[ i ]; if ( pThumbnail->GetReplayItem() == pReplayItem ) return pThumbnail; } return NULL; } void CReplayBrowserThumbnailRow::ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( "resource/ui/replaybrowser/thumbnailrow.res", "GAME" ); } void CReplayBrowserThumbnailRow::PerformLayout() { for ( int i = 0; i < m_vecThumbnails.Count(); ++i ) { CReplayBrowserThumbnail *pThumbnail = m_vecThumbnails[ i ]; pThumbnail->SetPos( i * ( pThumbnail->GetWide() + 2 * REPLAY_BORDER_WIDTH ), 0 ); bool bShouldBeVisible = pThumbnail->m_hReplayItem != REPLAY_HANDLE_INVALID; if ( pThumbnail->IsVisible() != bShouldBeVisible ) { pThumbnail->SetVisible( bShouldBeVisible ); } } BaseClass::PerformLayout(); } //----------------------------------------------------------------------------- CBaseThumbnailCollection::CBaseThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager ) : EditablePanel( pParent, pName ), m_pReplayItemManager( pReplayItemManager ), m_nStartX( XRES(15) ), m_pCaratLabel( NULL ), m_pTitleLabel( NULL ), m_pNoReplayItemsLabel( NULL ), m_pRenderAllButton( NULL ) { m_pParentListPanel = static_cast< CReplayListPanel * >( pParent ); m_pShowNextButton = NULL; m_pShowPrevButton = NULL; m_iViewingPage = 0; m_nReplayThumbnailsPerRow = 6; m_nMaxRows = 2; } void CBaseThumbnailCollection::AddReplay( const IQueryableReplayItem *pItem ) { m_vecReplays.AddToTail( pItem->GetItemHandle() ); } void CBaseThumbnailCollection::CleanupUIForReplayItem( ReplayItemHandle_t hReplayItem ) { IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( hReplayItem ); Assert( pReplayItem ); if ( !pReplayItem ) return; // Find the replay thumbnail CReplayBrowserThumbnailRow *pThumbnailRow = FindReplayItemThumbnailRow( pReplayItem ); if ( !pThumbnailRow ) { AssertMsg( 0, "REPLAY: Should have found replay thumbnail row while attempting to delete it from the browser." ); return; } // Remove it from the replay list and refresh the page m_vecReplays.FindAndRemove( hReplayItem ); UpdateViewingPage(); InvalidateLayout(); } int CBaseThumbnailCollection::GetRowStartY() { int nVerticalBuffer = YRES( 15 ); Panel *pLowestPanel = m_pReplayItemManager->GetItemCount() == 0 ? m_pNoReplayItemsLabel : GetLowestPanel( nVerticalBuffer ); int x,y; pLowestPanel->GetPos( x,y ); bool bMakeRoomForNextPrev = (m_pShowPrevButton && m_pShowNextButton && (m_pShowPrevButton->IsVisible() || m_pShowNextButton->IsVisible())); if ( bMakeRoomForNextPrev ) { nVerticalBuffer += m_pShowPrevButton->GetTall(); } return y + pLowestPanel->GetTall() + nVerticalBuffer; } CReplayBrowserThumbnailRow *CBaseThumbnailCollection::FindReplayItemThumbnailRow( const IQueryableReplayItem *pReplayItem ) { AssertValidReadPtr( pReplayItem ); FOR_EACH_VEC( m_vecRows, i ) { CReplayBrowserThumbnailRow *pRow = m_vecRows[ i ]; // If the replay thumbnail exists in the given row, return it if ( pRow->FindThumbnail( pReplayItem ) ) return pRow; } return NULL; } void CBaseThumbnailCollection::RemoveEmptyRows() { // Get a pointer to the row CUtlVector< CReplayBrowserThumbnailRow * > vecRowsToDelete; FOR_EACH_VEC( m_vecRows, i ) { CReplayBrowserThumbnailRow *pRow = m_vecRows[ i ]; if ( pRow->GetNumVisibleReplayItems() == 0 ) { vecRowsToDelete.AddToTail( pRow ); } } // Delete any rows FOR_EACH_VEC( vecRowsToDelete, i ) { // Remove it int nElement = m_vecRows.Find( vecRowsToDelete[ i ] ); m_vecRows[ nElement ]->MarkForDeletion(); m_vecRows.Remove( nElement ); } // If we deleted any rows... if ( vecRowsToDelete.Count() ) { // Reposition and repaint ReplayUI_GetBrowserPanel()->InvalidateLayout(); ReplayUI_GetBrowserPanel()->Repaint(); // If we don't have any rows left... if ( !m_vecRows.Count() ) { m_pParentListPanel->RemoveCollection( this ); } } } void CBaseThumbnailCollection::RemoveAll() { m_vecReplays.RemoveAll(); RemoveEmptyRows(); UpdateViewingPage(); InvalidateLayout(); } void CBaseThumbnailCollection::OnUpdated() { m_iViewingPage = 0; UpdateViewingPage(); InvalidateLayout( true, false ); } void CBaseThumbnailCollection::OnCommand( const char *pCommand ) { if ( FStrEq( pCommand, "render_queued_replays" ) ) { ::ReplayUI_ShowRenderDialog( this, REPLAY_HANDLE_INVALID, false, -1 ); return; } else if ( FStrEq( pCommand, "show_next" ) ) { int iThumbnailsPerPage = (m_nReplayThumbnailsPerRow * m_nMaxRows); m_iViewingPage = clamp( m_iViewingPage + 1, 0, Ceil2Int((float)m_vecReplays.Count() / (float)iThumbnailsPerPage) ); UpdateViewingPage(); return; } else if ( FStrEq( pCommand, "show_prev" ) ) { int iThumbnailsPerPage = (m_nReplayThumbnailsPerRow * m_nMaxRows); m_iViewingPage = clamp( m_iViewingPage - 1, 0, Ceil2Int((float)m_vecReplays.Count() / (float)iThumbnailsPerPage) ); UpdateViewingPage(); return; } BaseClass::OnCommand( pCommand ); } void CBaseThumbnailCollection::UpdateViewingPage( void ) { int iThumbnailsPerPage = (m_nReplayThumbnailsPerRow * m_nMaxRows); int iFirstReplayOnPage = (m_iViewingPage * iThumbnailsPerPage); // If the page we're on is not the first page, and we have no replays on it, move back a page. while (iFirstReplayOnPage >= m_vecReplays.Count() && m_iViewingPage > 0 ) { m_iViewingPage--; iFirstReplayOnPage = (m_iViewingPage * iThumbnailsPerPage); } for ( int i = 0; i < iThumbnailsPerPage; i++ ) { int iReplayIndex = (iFirstReplayOnPage + i); int iRow = floor( i / (float)m_nReplayThumbnailsPerRow ); int iColumn = i % m_nReplayThumbnailsPerRow; // Hit the max number of rows we show? if ( iRow >= m_nMaxRows ) break; // Need a new row? if ( iRow >= m_vecRows.Count() ) { // If we don't actually have any more replays, we don't need to make the new row. if ( iReplayIndex >= m_vecReplays.Count() ) break; // Create a new row and add there CReplayBrowserThumbnailRow *pNewRow = new CReplayBrowserThumbnailRow( this, "ThumbnailRow", m_pReplayItemManager ); m_vecRows.AddToTail( pNewRow ); InvalidateLayout(); } // Need another thumbnail in this row? if ( iColumn >= m_vecRows[iRow]->GetNumReplayItems() ) { // Hit the max number of thumbnails in this row? if ( iColumn >= m_nReplayThumbnailsPerRow ) break; m_vecRows[iRow]->AddReplayThumbnail( REPLAY_HANDLE_INVALID ); } if ( iReplayIndex >= m_vecReplays.Count() ) { m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetReplayItem( REPLAY_HANDLE_INVALID ); m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetVisible( false ); } else { m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetReplayItem( m_vecReplays[iReplayIndex] ); m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetVisible( true ); } } // Update the button counts wchar_t wszTemp[256]; wchar_t wszCount[10]; int iNextReplays = clamp( m_vecReplays.Count() - iFirstReplayOnPage - iThumbnailsPerPage, 0, iThumbnailsPerPage ); if ( iNextReplays > 0 ) { _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", iNextReplays ); g_pVGuiLocalize->ConstructString( wszTemp, sizeof( wszTemp ), g_pVGuiLocalize->Find("#Replay_NextX"), 1, wszCount ); SetDialogVariable( "nextbuttontext", wszTemp ); m_pShowNextButton->SetVisible( true ); } else { m_pShowNextButton->SetVisible( false ); } int iPrevReplays = clamp( iFirstReplayOnPage, 0, iThumbnailsPerPage ); if ( iPrevReplays > 0 ) { _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", iPrevReplays ); g_pVGuiLocalize->ConstructString( wszTemp, sizeof( wszTemp ), g_pVGuiLocalize->Find("#Replay_PrevX"), 1, wszCount ); SetDialogVariable( "prevbuttontext", wszTemp ); m_pShowPrevButton->SetVisible( true ); } else { m_pShowPrevButton->SetVisible( false ); } RemoveEmptyRows(); // We may have changed our size, so we need to tell our parent that it should re-layout m_pParentListPanel->InvalidateLayout(); } void CBaseThumbnailCollection::ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( "resource/ui/replaybrowser/thumbnailcollection.res", "GAME" ); m_pCaratLabel = dynamic_cast< CExLabel * >( FindChildByName( "CaratLabel" ) ); m_pTitleLabel = dynamic_cast< CExLabel * >( FindChildByName( "TitleLabel" ) ); m_pNoReplayItemsLabel = dynamic_cast< CExLabel * >( FindChildByName( "NoReplayItemsLabel" ) ); m_pShowNextButton = dynamic_cast< CExButton * >( FindChildByName( "ShowNextButton" ) ); if ( m_pShowNextButton ) { m_pShowNextButton->AddActionSignalTarget( this ); } m_pShowPrevButton = dynamic_cast< CExButton * >( FindChildByName( "ShowPrevButton" ) ); if ( m_pShowPrevButton ) { m_pShowPrevButton->AddActionSignalTarget( this ); } UpdateViewingPage(); } void CBaseThumbnailCollection::PerformLayout() { int nUnconvertedBgWidth = GetWide(); // Layout no replay items label m_pNoReplayItemsLabel->SetPos( ( GetWide() - m_pNoReplayItemsLabel->GetWide() ) / 2, YRES( 40 ) ); m_pNoReplayItemsLabel->SetVisible( !m_pReplayItemManager->GetItemCount() ); m_pNoReplayItemsLabel->SetContentAlignment( Label::a_center ); int nStartY = YRES(5); // Update the title count wchar_t wszCount[10]; _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_vecReplays.Count() ); wchar_t wszTemp[256]; const char *pszLocString = IsMovieCollection() ? "#Replay_SavedMovies" : "#Replay_UnrenderedReplays"; g_pVGuiLocalize->ConstructString( wszTemp, sizeof( wszTemp ), g_pVGuiLocalize->Find(pszLocString), 1, wszCount ); SetDialogVariable( "titleandcount", wszTemp ); // Setup labels for unconverted replay display LayoutUpperPanels( nStartY, nUnconvertedBgWidth ); int nCurrentY = GetRowStartY(); // Position our prev button int nButtonX = (GetWide() - m_pShowPrevButton->GetWide()) * 0.5; if ( m_pShowPrevButton ) { m_pShowPrevButton->SetPos( nButtonX, nCurrentY - m_pShowPrevButton->GetTall() - YRES(2) ); nCurrentY += YRES(2); } // Setup converted row positions for ( int i = 0; i < m_vecRows.Count(); ++i ) { CReplayBrowserThumbnailRow *pRow = m_vecRows[ i ]; pRow->SetPos( m_nStartX, nCurrentY ); pRow->InvalidateLayout( true, true ); int nRowTall = pRow->GetTall(); nCurrentY += nRowTall; } int nTotalHeight = nCurrentY; // Position our next button if ( m_pShowNextButton ) { m_pShowNextButton->SetPos( nButtonX, nCurrentY ); bool bMakeRoomForNextPrev = (m_pShowPrevButton && m_pShowNextButton && (m_pShowPrevButton->IsVisible() || m_pShowNextButton->IsVisible())); if ( bMakeRoomForNextPrev ) { nTotalHeight += m_pShowNextButton->GetTall() + YRES(5); } } LayoutBackgroundPanel( nUnconvertedBgWidth, nTotalHeight ); // TODO: Resizing can cause an InvalidateLayout() call if the new & old dimensions differ, // perhaps calling this here is not the best idea. // Adjust total height of panel SetTall( nTotalHeight ); BaseClass::PerformLayout(); } //----------------------------------------------------------------------------- CReplayThumbnailCollection::CReplayThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager ) : BaseClass( pParent, pName, pReplayItemManager ), m_pWarningLabel( NULL ), m_pUnconvertedBg( NULL ) { // Create additional panels for unconverted rows m_pLinePanel = new Panel( this, "Line" ); m_pWarningLabel = new CExLabel( this, "WarningLabel", "#Replay_ConversionWarning" ); m_pUnconvertedBg = new Panel( this, "UnconvertedBg" ); m_pRenderAllButton = new CExButton( this, "RenderAllButton", "#Replay_RenderAll" ); } bool CReplayThumbnailCollection::IsMovieCollection() const { return false; } void CReplayThumbnailCollection::PerformLayout() { BaseClass::PerformLayout(); } void CReplayThumbnailCollection::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); #if defined( TF_CLIENT_DLL ) if ( m_pUnconvertedBg ) { vgui::IBorder *pBorder = pScheme->GetBorder( "ButtonBorder" ); m_pUnconvertedBg->SetBorder( pBorder ); SetPaintBorderEnabled( true ); } #else SetPaintBorderEnabled( false ); #endif // Get current key binding for "save_replay", if any. const char *pBoundKey = engine->Key_LookupBinding( "save_replay" ); if ( !pBoundKey || FStrEq( pBoundKey, "(null)" ) ) { m_pNoReplayItemsLabel->SetText( "#Replay_NoKeyBoundNoReplays" ); } else { char szKey[16]; Q_snprintf( szKey, sizeof(szKey), "%s", pBoundKey ); wchar_t wKey[16]; wchar_t wLabel[256]; g_pVGuiLocalize->ConvertANSIToUnicode( szKey, wKey, sizeof( wKey ) ); g_pVGuiLocalize->ConstructString( wLabel, sizeof( wLabel ), g_pVGuiLocalize->Find("#Replay_NoReplays" ), 1, wKey ); m_pNoReplayItemsLabel->SetText( wLabel ); } } void CReplayThumbnailCollection::LayoutUpperPanels( int nStartY, int nBgWidth ) { int nUnconvertedY = nStartY + 2 * REPLAY_BUFFER_HEIGHT; if ( !m_pTitleLabel ) return; m_pTitleLabel->SizeToContents(); m_pTitleLabel->SetBounds( m_nStartX, nUnconvertedY, GetWide(), m_pTitleLabel->GetTall() ); m_pTitleLabel->SetVisible( true ); int cx, cy; int nWarningStartY = nUnconvertedY + m_pTitleLabel->GetTall() + REPLAY_BUFFER_HEIGHT; m_pWarningLabel->GetContentSize( cx, cy ); m_pWarningLabel->SetBounds( m_nStartX, nWarningStartY, 2 * GetWide() / 3, cy ); // For when "convert all" button is shown m_pWarningLabel->SetVisible( m_pReplayItemManager->GetItemCount() > 0 ); // Setup carat label if ( m_pCaratLabel ) { m_pCaratLabel->SizeToContents(); m_pCaratLabel->SetPos( m_nStartX - m_pCaratLabel->GetWide() - XRES(3), nUnconvertedY ); m_pCaratLabel->SetVisible( true ); } // Setup line int nLineBuffer = m_pReplayItemManager->GetItemCount() > 0 ? YRES( 15 ) : nStartY; int nLineY = nWarningStartY + nLineBuffer; m_pLinePanel->SetBounds( 0, nLineY, nBgWidth, 1 ); m_pLinePanel->SetVisible( true ); int nButtonX = (GetWide() - m_pShowPrevButton->GetWide()) * 0.5; if ( m_pShowPrevButton ) { m_pShowPrevButton->SetPos( nButtonX, nLineY ); } } void CReplayThumbnailCollection::LayoutBackgroundPanel( int nWide, int nTall ) { // Setup bounds for dark background, if there are unconverted replays in this collection if ( m_pUnconvertedBg ) { m_pUnconvertedBg->SetBounds( 0, 0, nWide, nTall ); m_pUnconvertedBg->SetVisible( true ); // Setup convert all button int nWarningLabelX, nWarningLabelY; m_pWarningLabel->GetPos( nWarningLabelX, nWarningLabelY ); int nRenderAllX = m_pUnconvertedBg->GetWide() - m_pRenderAllButton->GetWide() - XRES( 5 ); m_pRenderAllButton->SetPos( nRenderAllX, nWarningLabelY - m_pRenderAllButton->GetTall()/2 ); m_pRenderAllButton->SetVisible( m_pReplayItemManager->GetItemCount() > 0 ); } } Panel *CReplayThumbnailCollection::GetLowestPanel( int &nVerticalBuffer ) { nVerticalBuffer = YRES( 8 ); return m_pLinePanel; } //----------------------------------------------------------------------------- CMovieThumbnailCollection::CMovieThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager, int nDay, int nMonth, int nYear, bool bShowSavedMoviesLabel ) : BaseClass( pParent, pName, pReplayItemManager ) { Init( nDay, nMonth, nYear, bShowSavedMoviesLabel ); } CMovieThumbnailCollection::CMovieThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager, bool bShowSavedMoviesLabel ) : BaseClass( pParent, pName, pReplayItemManager ) { Init( -1, -1, -1, bShowSavedMoviesLabel ); } void CMovieThumbnailCollection::Init( int nDay, int nMonth, int nYear, bool bShowSavedMoviesLabel ) { m_nDay = nDay; m_nMonth = nMonth; m_nYear = nYear; if ( m_nDay == OLDER_MOVIES_COLLECTION ) { m_pDateLabel = new CExLabel( this, "DateLabel", "#Replay_OlderMovies" ); } else { m_pDateLabel = m_nDay >= 0 ? new CExLabel( this, "DateLabel", CReplayTime::GetLocalizedDate( g_pVGuiLocalize, nDay, nMonth, nYear ) ) : NULL; } m_bShowSavedMoviesLabel = bShowSavedMoviesLabel; } bool CMovieThumbnailCollection::IsMovieCollection() const { return true; } void CMovieThumbnailCollection::PerformLayout() { BaseClass::PerformLayout(); // Movies have multiple collections under a single header. So we use the total movies, not the amount in this collection. if ( g_pReplayMovieManager ) { wchar_t wszCount[10]; _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", g_pReplayMovieManager->GetMovieCount() ); wchar_t wszTemp[256]; g_pVGuiLocalize->ConstructString( wszTemp, sizeof( wszTemp ), g_pVGuiLocalize->Find("#Replay_SavedMovies"), 1, wszCount ); SetDialogVariable( "titleandcount", wszTemp ); } if ( m_pDateLabel ) { m_pDateLabel->SetVisible( m_pReplayItemManager->GetItemCount() ); } } void CMovieThumbnailCollection::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); if ( m_pDateLabel ) { m_pDateLabel->SetVisible( true ); } if ( m_pCaratLabel ) { m_pCaratLabel->SetVisible( m_bShowSavedMoviesLabel ); } if ( m_pTitleLabel ) { m_pTitleLabel->SetVisible( m_bShowSavedMoviesLabel ); } m_pNoReplayItemsLabel->SetText( "#Replay_NoMovies" ); } void CMovieThumbnailCollection::LayoutUpperPanels( int nStartY, int nBgWidth ) { if ( m_pTitleLabel && m_pTitleLabel->IsVisible() ) { m_pTitleLabel->SizeToContents(); m_pTitleLabel->SetPos( m_nStartX, nStartY ); m_pCaratLabel->SizeToContents(); m_pCaratLabel->SetPos( m_nStartX - m_pCaratLabel->GetWide() - XRES(3), nStartY + ( m_pCaratLabel->GetTall() - m_pCaratLabel->GetTall() ) / 2 ); nStartY += m_pTitleLabel->GetTall() + YRES( 5 ); } // Date label if ( m_pDateLabel && m_pDateLabel->IsVisible() ) { m_pDateLabel->SizeToContents(); m_pDateLabel->SetPos( m_nStartX, nStartY ); } } Panel *CMovieThumbnailCollection::GetLowestPanel( int &nVerticalBuffer ) { nVerticalBuffer = YRES( 8 ); return m_pDateLabel ? m_pDateLabel : m_pTitleLabel; } bool CMovieThumbnailCollection::DoesDateMatch( int nDay, int nMonth, int nYear ) { return ( nDay == m_nDay ) && ( nMonth == m_nMonth ) && ( nYear == m_nYear ); } #endif