source-engine/game/client/replay/vgui/replaybrowserlistitempanel.cpp
2022-03-01 23:00:42 +03:00

1091 lines
33 KiB
C++

//========= 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