Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1090 lines
33 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "cbase.h"
  5. #if defined( REPLAY_ENABLED )
  6. #include "replaybrowserlistitempanel.h"
  7. #include "replaybrowsermainpanel.h"
  8. #include "replaybrowserlistpanel.h"
  9. #include "replaybrowserrenderdialog.h"
  10. #include "replaybrowsermovieplayerpanel.h"
  11. #include "vgui/IInput.h"
  12. #include "vgui/IVGui.h"
  13. #include "filesystem.h"
  14. #include "replay/screenshot.h"
  15. #include "replay/ireplaymanager.h"
  16. #include "confirm_dialog.h"
  17. #include "replay/ireplaymoviemanager.h"
  18. #include "econ/econ_controls.h"
  19. //-----------------------------------------------------------------------------
  20. using namespace vgui;
  21. extern IReplayMovieManager *g_pReplayMovieManager;
  22. //-----------------------------------------------------------------------------
  23. #define REPLAY_BORDER_WIDTH XRES(2)
  24. #define REPLAY_BORDER_HEIGHT YRES(2)
  25. #define REPLAY_BUFFER_HEIGHT XRES(5)
  26. //-----------------------------------------------------------------------------
  27. CReplayScreenshotSlideshowPanel::CReplayScreenshotSlideshowPanel( Panel *pParent, const char *pName, ReplayHandle_t hReplay )
  28. : CSlideshowPanel( pParent, pName ),
  29. m_hReplay( hReplay )
  30. {
  31. CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( hReplay );
  32. // Add all screenshots for the given replay
  33. char szImage[ MAX_OSPATH ];
  34. for ( int i = 0; i < pReplay->GetScreenshotCount(); ++i )
  35. {
  36. const CReplayScreenshot *pScreenshot = pReplay->GetScreenshot( i );
  37. V_snprintf( szImage, sizeof( szImage ), "replay/thumbnails/%s.vtf", pScreenshot->m_szBaseFilename );
  38. // Add image to list of slideshow images
  39. AddImage( szImage );
  40. if ( i == 0 )
  41. {
  42. // Set first image
  43. GetImagePanel()->SetImage( szImage );
  44. }
  45. }
  46. }
  47. void CReplayScreenshotSlideshowPanel::PerformLayout()
  48. {
  49. BaseClass::PerformLayout();
  50. }
  51. //-----------------------------------------------------------------------------
  52. CReplayBrowserThumbnail::CReplayBrowserThumbnail( Panel *pParent, const char *pName, QueryableReplayItemHandle_t hReplayItem,
  53. IReplayItemManager *pReplayItemManager )
  54. : CReplayBasePanel( pParent, pName ),
  55. m_hReplayItem( hReplayItem ),
  56. m_pReplayItemManager( pReplayItemManager ),
  57. m_bMouseOver( false ),
  58. m_pMoviePlayer( NULL ),
  59. m_flLastMovieScrubTime( 0.0f ),
  60. m_flHoverStartTime( 0.0f ),
  61. m_flLastProgressChangeTime( 0.0f )
  62. {
  63. SetScheme( "ClientScheme" );
  64. ivgui()->AddTickSignal( GetVPanel(), 10 );
  65. // Add the list panel as an action signal target
  66. // NOTE: Vile hack.
  67. Panel *pTarget = GetParent();
  68. while ( pTarget )
  69. {
  70. if ( !V_strcmp( "BasePage", pTarget->GetName() ) )
  71. break;
  72. pTarget = pTarget->GetParent();
  73. }
  74. Assert( pTarget );
  75. AddActionSignalTarget( pTarget );
  76. m_pScreenshotThumb = new CCrossfadableImagePanel( this, "ScreenshotThumbnail" );
  77. m_pTitle = new Label( this, "TitleLabel", "" );
  78. m_pDownloadLabel = new Label( this, "DownloadLabel", "" );
  79. m_pRecordingInProgressLabel = new Label( this, "RecordingInProgressLabel", "" );
  80. m_pDownloadProgress = new ProgressBar( this, "DownloadProgress" );
  81. m_pDownloadButton = new CExButton( this, "DownloadButton", "#Replay_Download" );
  82. m_pDeleteButton = new CExButton( this, "DeleteButton", "#X_Delete" );
  83. m_pErrorLabel = new Label( this, "ErrorLabel", "" );
  84. m_pDownloadOverlay = new Panel( this, "DownloadOverlay" );
  85. m_pBorderPanel = new EditablePanel( this, "BorderPanel" );
  86. m_pScreenshotThumb->InstallMouseHandler( this );
  87. m_pDownloadButton->AddActionSignalTarget( this );
  88. m_pDownloadButton->SetCommand( new KeyValues( "download" ) );
  89. m_pDeleteButton->AddActionSignalTarget( this );
  90. m_pDeleteButton->SetCommand( new KeyValues( "delete_replayitem" ) );
  91. SetProportional( true );
  92. SetReplayItem( hReplayItem );
  93. }
  94. CReplayBrowserThumbnail::~CReplayBrowserThumbnail()
  95. {
  96. // Clear the download event handler ptr
  97. CGenericClassBasedReplay *pReplay = GetReplay();
  98. if ( pReplay )
  99. {
  100. pReplay->m_pDownloadEventHandler = NULL;
  101. }
  102. SetupReplayItemUserData( NULL );
  103. ivgui()->RemoveTickSignal( GetVPanel() );
  104. }
  105. void CReplayBrowserThumbnail::SetReplayItem( QueryableReplayItemHandle_t hReplayItem )
  106. {
  107. if ( m_hReplayItem != REPLAY_HANDLE_INVALID && m_hReplayItem != hReplayItem )
  108. {
  109. IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( m_hReplayItem );
  110. if ( pReplayItem )
  111. {
  112. pReplayItem->SetUserData( NULL );
  113. CGenericClassBasedReplay *pTFReplay = ToGenericClassBasedReplay( pReplayItem->GetItemReplay() );
  114. pTFReplay->m_pDownloadEventHandler = NULL;
  115. }
  116. }
  117. m_hReplayItem = hReplayItem;
  118. if ( hReplayItem != REPLAY_HANDLE_INVALID )
  119. {
  120. // Set this as user data
  121. SetupReplayItemUserData( (void *)this );
  122. }
  123. InvalidateLayout();
  124. }
  125. void CReplayBrowserThumbnail::SetupReplayItemUserData( void *pUserData )
  126. {
  127. IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( m_hReplayItem );
  128. if ( pReplayItem )
  129. {
  130. pReplayItem->SetUserData( pUserData );
  131. }
  132. }
  133. void CReplayBrowserThumbnail::OnTick()
  134. {
  135. const CGenericClassBasedReplay *pReplay = GetReplay();
  136. if ( !pReplay )
  137. return;
  138. // Get out if the delete confirmation dialog is up
  139. if ( vgui::input()->GetAppModalSurface() )
  140. return;
  141. // Need to update layout if status has changed, since some buttons may need to be shifted around
  142. // TODO: Only layout when necessary
  143. InvalidateLayout( true, false );
  144. // Get mouse position and store state
  145. int x,y;
  146. vgui::input()->GetCursorPos(x, y);
  147. bool bOldMouseOver = m_bMouseOver;
  148. m_bMouseOver = m_pBorderPanel->IsWithin(x,y);
  149. // If we are just starting to hover over the thumbnail, mark the time
  150. if ( bOldMouseOver != m_bMouseOver && m_bMouseOver )
  151. {
  152. m_flHoverStartTime = gpGlobals->realtime;
  153. }
  154. const char *pBorderName = m_bMouseOver ? "ReplayHighlightBorder" : "ReplayDefaultBorder";
  155. IBorder *pBorder = scheme()->GetIScheme( GetScheme() )->GetBorder( pBorderName );
  156. m_pBorderPanel->SetBorder( pBorder );
  157. // Set visibility of buttons and such - a player may have saved their replay but not died yet,
  158. // in which case pReplay->m_bComplete will be false.
  159. const IQueryableReplayItem *pItem = m_pReplayItemManager->GetItem( m_hReplayItem );
  160. bool bIncomplete = !pReplay->m_bComplete;
  161. bool bDownloadPhase = !pItem->IsItemAMovie() && pReplay->m_nStatus == CReplay::REPLAYSTATUS_DOWNLOADPHASE;
  162. bool bErrorState = pReplay->m_nStatus == CReplay::REPLAYSTATUS_ERROR;
  163. m_pDownloadButton->SetVisible( false );
  164. m_pDeleteButton->SetVisible( bErrorState || bDownloadPhase );
  165. m_pDownloadOverlay->SetVisible( bErrorState || bDownloadPhase || bIncomplete );
  166. m_pDownloadProgress->SetVisible( bDownloadPhase );
  167. m_pErrorLabel->SetVisible( bErrorState );
  168. m_pRecordingInProgressLabel->SetVisible( bIncomplete );
  169. UpdateProgress( bDownloadPhase, pReplay );
  170. }
  171. void CReplayBrowserThumbnail::UpdateProgress( bool bDownloadPhase, const CReplay *pReplay )
  172. {
  173. if ( !bDownloadPhase )
  174. return;
  175. // Get current download progress
  176. const float flProgress = g_pClientReplayContext->GetReplayManager()->GetDownloadProgress( pReplay );
  177. // Has progress changed?
  178. const int nNewProgress = 100 * flProgress;
  179. const int nOldProgress = 100 * m_pDownloadProgress->GetProgress();
  180. const float flCurTime = gpGlobals->realtime;
  181. if ( nNewProgress != nOldProgress )
  182. {
  183. m_flLastProgressChangeTime = flCurTime;
  184. }
  185. // Set download progress
  186. m_pDownloadProgress->SetProgress( flProgress );
  187. const char *pToken = "#Replay_Waiting";
  188. if ( ReplayUI_GetBrowserPanel() && ( flCurTime - ReplayUI_GetBrowserPanel()->GetTimeOpened() > 2.0f ) )
  189. {
  190. // Use "downloading" string if progress has changed in the last two seconds
  191. if ( ( flCurTime - m_flLastProgressChangeTime ) < 2.0f )
  192. {
  193. pToken = "#Replay_Downloading";
  194. }
  195. }
  196. const wchar_t *pLocalizedText = g_pVGuiLocalize->Find( pToken );
  197. if ( pLocalizedText )
  198. {
  199. // Add animating '...' to end of string
  200. wchar_t wszText[128] = { 0 };
  201. V_wcscpy_safe( wszText, pLocalizedText );
  202. unsigned int nNumPeriods = fmod( gpGlobals->realtime * 2.0f, 4.0f ); // Max of 3 dots
  203. const unsigned int nLen = wcslen( wszText );
  204. V_wcscat_safe( wszText, L"..." );
  205. wszText[ Min( nLen + nNumPeriods, (unsigned int)sizeof( wszText ) - 1 ) ] = L'\0';
  206. m_pDownloadLabel->SetText( wszText );
  207. m_pDownloadLabel->SizeToContents();
  208. m_pDownloadLabel->SetWide( m_pDownloadProgress->GetWide() );
  209. m_pDownloadLabel->SetPos( 3, (m_pDownloadProgress->GetTall() - m_pDownloadLabel->GetTall()) / 2 );
  210. }
  211. }
  212. void CReplayBrowserThumbnail::ApplySchemeSettings( vgui::IScheme *pScheme )
  213. {
  214. BaseClass::ApplySchemeSettings( pScheme );
  215. LoadControlSettings( "resource/ui/replaybrowser/listthumbnail.res", "GAME" );
  216. // Get default from the .res file
  217. m_clrDefaultBg = GetBgColor();
  218. m_clrHighlight = GetSchemeColor( "TanDark", Color( 255, 255, 255, 255 ), pScheme );
  219. }
  220. void CReplayBrowserThumbnail::PerformLayout()
  221. {
  222. BaseClass::PerformLayout();
  223. const CGenericClassBasedReplay *pReplay = GetReplay();
  224. if ( !pReplay )
  225. return;
  226. AssertValidReadPtr( pReplay );
  227. // Get thumbnail for first screenshot
  228. char szImage[MAX_OSPATH] = { '\0' };
  229. bool bHasScreenshots = false;
  230. if ( pReplay->GetScreenshotCount() )
  231. {
  232. // Use first screenshot for thumbnail
  233. bHasScreenshots = true;
  234. const CReplayScreenshot *pScreenshot = pReplay->GetScreenshot( 0 );
  235. V_snprintf( szImage, sizeof( szImage ), "replay/thumbnails/%s.vtf", pScreenshot->m_szBaseFilename );
  236. }
  237. // See if it exists
  238. char szSearch[MAX_OSPATH];
  239. V_snprintf( szSearch, sizeof( szSearch ), "materials/vgui/%s", szImage );
  240. bool bShowScreenshotThumb = true;
  241. if ( !bHasScreenshots || !g_pFullFileSystem || !g_pFullFileSystem->FileExists( szSearch, "GAME" ) )
  242. {
  243. // Hide it
  244. bShowScreenshotThumb = false;
  245. }
  246. // Scale the screenshot so that it clips off the dead area of the power of 2 texture
  247. float flScale = pReplay->GetScreenshotCount() == 0 ? 1.0f : ( (float)m_pScreenshotThumb->GetWide() / ( .95f * pReplay->GetScreenshot( 0 )->m_nWidth ) );
  248. m_pScreenshotThumb->SetScaleAmount( flScale );
  249. m_pScreenshotThumb->SetShouldScaleImage( true );
  250. if ( bShowScreenshotThumb )
  251. {
  252. // We don't want to actually hide it (via SetVisible()), since we need to get mouse click events from the image panel
  253. m_pScreenshotThumb->SetImage( szImage );
  254. }
  255. // Setup progress bar & label
  256. m_pDownloadProgress->SetWide( GetWide() * .95f );
  257. m_pDownloadProgress->SetSegmentInfo( 0, 1 );
  258. m_pDownloadProgress->SetBarInset( 2 );
  259. m_pDownloadProgress->SetMargin( 2 );
  260. m_pDownloadProgress->SetPos(
  261. (GetWide() - m_pDownloadProgress->GetWide()) / 2,
  262. (m_pScreenshotThumb->GetTall() - m_pDownloadProgress->GetTall()) / 2
  263. );
  264. m_pDownloadLabel->SetParent( m_pDownloadProgress );
  265. m_pDownloadLabel->SetWide( m_pDownloadProgress->GetWide() );
  266. m_pDownloadLabel->SetPos( 3, (m_pDownloadProgress->GetTall() - m_pDownloadLabel->GetTall()) / 2 );
  267. m_pErrorLabel->SizeToContents();
  268. m_pErrorLabel->SetPos(
  269. (GetWide() - m_pErrorLabel->GetWide()) / 2,
  270. (m_pScreenshotThumb->GetTall() - m_pErrorLabel->GetTall()) / 2
  271. );
  272. // Center the title control horizontally
  273. int nThumbX, nThumbY, nThumbW, nThumbH;
  274. UpdateTitleText();
  275. m_pScreenshotThumb->GetBounds( nThumbX, nThumbY, nThumbW, nThumbH );
  276. m_pDownloadOverlay->SetBounds( 0, 0, GetWide(), GetTall() );
  277. if ( m_pMoviePlayer )
  278. {
  279. m_pMoviePlayer->SetBounds( nThumbX, nThumbY, nThumbW, nThumbH );
  280. }
  281. // Setup recording-in-progress
  282. m_pRecordingInProgressLabel->SetBounds( nThumbX, nThumbY, nThumbW, nThumbH );
  283. m_pRecordingInProgressLabel->InvalidateLayout( true, false ); // Need this for centerwrap to work properly
  284. bool bDownloadPhase = pReplay->m_nStatus == CReplay::REPLAYSTATUS_DOWNLOADPHASE;
  285. int nButtonWidth = 9 * GetWide() / 10;
  286. int nButtonX = nThumbX + ( m_pScreenshotThumb->GetWide() - nButtonWidth ) / 2;
  287. int nDownloadButtonY, nDeleteButtonY;
  288. if ( bDownloadPhase )
  289. {
  290. nDownloadButtonY = nThumbY + 12;
  291. nDeleteButtonY = nThumbY + m_pScreenshotThumb->GetTall() - m_pDeleteButton->GetTall() - 12;
  292. }
  293. else
  294. {
  295. nDownloadButtonY = 0; // We don't care about this now, since it will not be visible
  296. nDeleteButtonY = ( m_pScreenshotThumb->GetTall() - m_pDeleteButton->GetTall() ) / 2;
  297. }
  298. // Adjust download button position
  299. m_pDownloadButton->SetPos( nButtonX, nDownloadButtonY );
  300. m_pDownloadButton->SetWide( nButtonWidth );
  301. }
  302. void CReplayBrowserThumbnail::OnDownloadClicked( KeyValues *pParams )
  303. {
  304. // Begin the download
  305. OnCommand( "download" );
  306. }
  307. void CReplayBrowserThumbnail::OnDeleteReplay( KeyValues *pParams )
  308. {
  309. OnCommand( "delete_replayitem" );
  310. }
  311. void CReplayBrowserThumbnail::OnCommand( const char *pCommand )
  312. {
  313. const CGenericClassBasedReplay *pReplay = GetReplay();
  314. AssertValidReadPtr( pReplay );
  315. if ( !pReplay )
  316. return;
  317. if ( FStrEq( pCommand, "details" ) ) // Display replay details?
  318. {
  319. char szCmd[256];
  320. V_snprintf( szCmd, sizeof( szCmd ), "details%i", (int)m_hReplayItem );
  321. PostActionSignal( new KeyValues( "Command", "command", szCmd ) );
  322. }
  323. else if ( FStrEq( pCommand, "delete_replayitem" ) ) // Delete the replay?
  324. {
  325. ReplayUI_GetBrowserPanel()->AttemptToDeleteReplayItem( this, m_hReplayItem, m_pReplayItemManager, -1 );
  326. }
  327. else
  328. {
  329. BaseClass::OnCommand( pCommand );
  330. }
  331. }
  332. void CReplayBrowserThumbnail::OnMousePressed( MouseCode code )
  333. {
  334. if ( code == MOUSE_LEFT )
  335. {
  336. OnCommand( "details" );
  337. }
  338. }
  339. void CReplayBrowserThumbnail::UpdateTitleText()
  340. {
  341. IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( m_hReplayItem );
  342. m_pTitle->SetText( pReplayItem->GetItemTitle() );
  343. }
  344. CGenericClassBasedReplay *CReplayBrowserThumbnail::GetReplay()
  345. {
  346. IQueryableReplayItem *pItem = m_pReplayItemManager->GetItem( m_hReplayItem );
  347. if ( !pItem )
  348. return NULL;
  349. return ToGenericClassBasedReplay( pItem->GetItemReplay() );
  350. }
  351. IQueryableReplayItem *CReplayBrowserThumbnail::GetReplayItem()
  352. {
  353. return m_pReplayItemManager->GetItem( m_hReplayItem );
  354. }
  355. //-----------------------------------------------------------------------------
  356. CReplayBrowserThumbnailRow::CReplayBrowserThumbnailRow( Panel *pParent, const char *pName, IReplayItemManager *pReplayItemManager )
  357. : BaseClass( pParent, pName ),
  358. m_pReplayItemManager( pReplayItemManager )
  359. {
  360. SetProportional( true );
  361. }
  362. void CReplayBrowserThumbnailRow::AddReplayThumbnail( const IQueryableReplayItem *pItem )
  363. {
  364. CReplayBrowserThumbnail *pThumbnail = new CReplayBrowserThumbnail( this, "ListThumbnail", pItem->GetItemHandle(), m_pReplayItemManager );
  365. m_vecThumbnails.AddToTail( pThumbnail );
  366. }
  367. void CReplayBrowserThumbnailRow::AddReplayThumbnail( QueryableReplayItemHandle_t hReplayItem )
  368. {
  369. CReplayBrowserThumbnail *pThumbnail = new CReplayBrowserThumbnail( this, "ListThumbnail", hReplayItem, m_pReplayItemManager );
  370. m_vecThumbnails.AddToTail( pThumbnail );
  371. }
  372. void CReplayBrowserThumbnailRow::DeleteReplayItemThumbnail( const IQueryableReplayItem *pReplayItem )
  373. {
  374. CReplayBrowserThumbnail *pThumbnail = FindThumbnail( pReplayItem );
  375. int nThumbnailElement = m_vecThumbnails.Find( pThumbnail );
  376. if ( nThumbnailElement == m_vecThumbnails.InvalidIndex() )
  377. {
  378. AssertMsg( 0, "REPLAY: Should have found replay thumbnail while attempting to delete it from the browser." );
  379. return;
  380. }
  381. // Delete the actual panel
  382. ivgui()->RemoveTickSignal( pThumbnail->GetVPanel() );
  383. pThumbnail->MarkForDeletion();
  384. // Remove from list of thumbs
  385. m_vecThumbnails.Remove( nThumbnailElement );
  386. }
  387. int CReplayBrowserThumbnailRow::GetNumVisibleReplayItems() const
  388. {
  389. int iCount = 0;
  390. FOR_EACH_VEC( m_vecThumbnails, i )
  391. {
  392. CReplayBrowserThumbnail *pThumbnail = m_vecThumbnails[ i ];
  393. if ( pThumbnail->IsVisible() )
  394. {
  395. iCount++;
  396. }
  397. }
  398. return iCount;
  399. }
  400. CReplayBrowserThumbnail *CReplayBrowserThumbnailRow::FindThumbnail( const IQueryableReplayItem *pReplayItem )
  401. {
  402. AssertValidReadPtr( pReplayItem );
  403. FOR_EACH_VEC( m_vecThumbnails, i )
  404. {
  405. CReplayBrowserThumbnail *pThumbnail = m_vecThumbnails[ i ];
  406. if ( pThumbnail->GetReplayItem() == pReplayItem )
  407. return pThumbnail;
  408. }
  409. return NULL;
  410. }
  411. void CReplayBrowserThumbnailRow::ApplySchemeSettings( vgui::IScheme *pScheme )
  412. {
  413. BaseClass::ApplySchemeSettings( pScheme );
  414. LoadControlSettings( "resource/ui/replaybrowser/thumbnailrow.res", "GAME" );
  415. }
  416. void CReplayBrowserThumbnailRow::PerformLayout()
  417. {
  418. for ( int i = 0; i < m_vecThumbnails.Count(); ++i )
  419. {
  420. CReplayBrowserThumbnail *pThumbnail = m_vecThumbnails[ i ];
  421. pThumbnail->SetPos( i * ( pThumbnail->GetWide() + 2 * REPLAY_BORDER_WIDTH ), 0 );
  422. bool bShouldBeVisible = pThumbnail->m_hReplayItem != REPLAY_HANDLE_INVALID;
  423. if ( pThumbnail->IsVisible() != bShouldBeVisible )
  424. {
  425. pThumbnail->SetVisible( bShouldBeVisible );
  426. }
  427. }
  428. BaseClass::PerformLayout();
  429. }
  430. //-----------------------------------------------------------------------------
  431. CBaseThumbnailCollection::CBaseThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager )
  432. : EditablePanel( pParent, pName ),
  433. m_pReplayItemManager( pReplayItemManager ),
  434. m_nStartX( XRES(15) ),
  435. m_pCaratLabel( NULL ),
  436. m_pTitleLabel( NULL ),
  437. m_pNoReplayItemsLabel( NULL ),
  438. m_pRenderAllButton( NULL )
  439. {
  440. m_pParentListPanel = static_cast< CReplayListPanel * >( pParent );
  441. m_pShowNextButton = NULL;
  442. m_pShowPrevButton = NULL;
  443. m_iViewingPage = 0;
  444. m_nReplayThumbnailsPerRow = 6;
  445. m_nMaxRows = 2;
  446. }
  447. void CBaseThumbnailCollection::AddReplay( const IQueryableReplayItem *pItem )
  448. {
  449. m_vecReplays.AddToTail( pItem->GetItemHandle() );
  450. }
  451. void CBaseThumbnailCollection::CleanupUIForReplayItem( ReplayItemHandle_t hReplayItem )
  452. {
  453. IQueryableReplayItem *pReplayItem = m_pReplayItemManager->GetItem( hReplayItem ); Assert( pReplayItem );
  454. if ( !pReplayItem )
  455. return;
  456. // Find the replay thumbnail
  457. CReplayBrowserThumbnailRow *pThumbnailRow = FindReplayItemThumbnailRow( pReplayItem );
  458. if ( !pThumbnailRow )
  459. {
  460. AssertMsg( 0, "REPLAY: Should have found replay thumbnail row while attempting to delete it from the browser." );
  461. return;
  462. }
  463. // Remove it from the replay list and refresh the page
  464. m_vecReplays.FindAndRemove( hReplayItem );
  465. UpdateViewingPage();
  466. InvalidateLayout();
  467. }
  468. int CBaseThumbnailCollection::GetRowStartY()
  469. {
  470. int nVerticalBuffer = YRES( 15 );
  471. Panel *pLowestPanel = m_pReplayItemManager->GetItemCount() == 0 ? m_pNoReplayItemsLabel : GetLowestPanel( nVerticalBuffer );
  472. int x,y;
  473. pLowestPanel->GetPos( x,y );
  474. bool bMakeRoomForNextPrev = (m_pShowPrevButton && m_pShowNextButton && (m_pShowPrevButton->IsVisible() || m_pShowNextButton->IsVisible()));
  475. if ( bMakeRoomForNextPrev )
  476. {
  477. nVerticalBuffer += m_pShowPrevButton->GetTall();
  478. }
  479. return y + pLowestPanel->GetTall() + nVerticalBuffer;
  480. }
  481. CReplayBrowserThumbnailRow *CBaseThumbnailCollection::FindReplayItemThumbnailRow( const IQueryableReplayItem *pReplayItem )
  482. {
  483. AssertValidReadPtr( pReplayItem );
  484. FOR_EACH_VEC( m_vecRows, i )
  485. {
  486. CReplayBrowserThumbnailRow *pRow = m_vecRows[ i ];
  487. // If the replay thumbnail exists in the given row, return it
  488. if ( pRow->FindThumbnail( pReplayItem ) )
  489. return pRow;
  490. }
  491. return NULL;
  492. }
  493. void CBaseThumbnailCollection::RemoveEmptyRows()
  494. {
  495. // Get a pointer to the row
  496. CUtlVector< CReplayBrowserThumbnailRow * > vecRowsToDelete;
  497. FOR_EACH_VEC( m_vecRows, i )
  498. {
  499. CReplayBrowserThumbnailRow *pRow = m_vecRows[ i ];
  500. if ( pRow->GetNumVisibleReplayItems() == 0 )
  501. {
  502. vecRowsToDelete.AddToTail( pRow );
  503. }
  504. }
  505. // Delete any rows
  506. FOR_EACH_VEC( vecRowsToDelete, i )
  507. {
  508. // Remove it
  509. int nElement = m_vecRows.Find( vecRowsToDelete[ i ] );
  510. m_vecRows[ nElement ]->MarkForDeletion();
  511. m_vecRows.Remove( nElement );
  512. }
  513. // If we deleted any rows...
  514. if ( vecRowsToDelete.Count() )
  515. {
  516. // Reposition and repaint
  517. ReplayUI_GetBrowserPanel()->InvalidateLayout();
  518. ReplayUI_GetBrowserPanel()->Repaint();
  519. // If we don't have any rows left...
  520. if ( !m_vecRows.Count() )
  521. {
  522. m_pParentListPanel->RemoveCollection( this );
  523. }
  524. }
  525. }
  526. void CBaseThumbnailCollection::RemoveAll()
  527. {
  528. m_vecReplays.RemoveAll();
  529. RemoveEmptyRows();
  530. UpdateViewingPage();
  531. InvalidateLayout();
  532. }
  533. void CBaseThumbnailCollection::OnUpdated()
  534. {
  535. m_iViewingPage = 0;
  536. UpdateViewingPage();
  537. InvalidateLayout( true, false );
  538. }
  539. void CBaseThumbnailCollection::OnCommand( const char *pCommand )
  540. {
  541. if ( FStrEq( pCommand, "render_queued_replays" ) )
  542. {
  543. ::ReplayUI_ShowRenderDialog( this, REPLAY_HANDLE_INVALID, false, -1 );
  544. return;
  545. }
  546. else if ( FStrEq( pCommand, "show_next" ) )
  547. {
  548. int iThumbnailsPerPage = (m_nReplayThumbnailsPerRow * m_nMaxRows);
  549. m_iViewingPage = clamp( m_iViewingPage + 1, 0, Ceil2Int((float)m_vecReplays.Count() / (float)iThumbnailsPerPage) );
  550. UpdateViewingPage();
  551. return;
  552. }
  553. else if ( FStrEq( pCommand, "show_prev" ) )
  554. {
  555. int iThumbnailsPerPage = (m_nReplayThumbnailsPerRow * m_nMaxRows);
  556. m_iViewingPage = clamp( m_iViewingPage - 1, 0, Ceil2Int((float)m_vecReplays.Count() / (float)iThumbnailsPerPage) );
  557. UpdateViewingPage();
  558. return;
  559. }
  560. BaseClass::OnCommand( pCommand );
  561. }
  562. void CBaseThumbnailCollection::UpdateViewingPage( void )
  563. {
  564. int iThumbnailsPerPage = (m_nReplayThumbnailsPerRow * m_nMaxRows);
  565. int iFirstReplayOnPage = (m_iViewingPage * iThumbnailsPerPage);
  566. // If the page we're on is not the first page, and we have no replays on it, move back a page.
  567. while (iFirstReplayOnPage >= m_vecReplays.Count() && m_iViewingPage > 0 )
  568. {
  569. m_iViewingPage--;
  570. iFirstReplayOnPage = (m_iViewingPage * iThumbnailsPerPage);
  571. }
  572. for ( int i = 0; i < iThumbnailsPerPage; i++ )
  573. {
  574. int iReplayIndex = (iFirstReplayOnPage + i);
  575. int iRow = floor( i / (float)m_nReplayThumbnailsPerRow );
  576. int iColumn = i % m_nReplayThumbnailsPerRow;
  577. // Hit the max number of rows we show?
  578. if ( iRow >= m_nMaxRows )
  579. break;
  580. // Need a new row?
  581. if ( iRow >= m_vecRows.Count() )
  582. {
  583. // If we don't actually have any more replays, we don't need to make the new row.
  584. if ( iReplayIndex >= m_vecReplays.Count() )
  585. break;
  586. // Create a new row and add there
  587. CReplayBrowserThumbnailRow *pNewRow = new CReplayBrowserThumbnailRow( this, "ThumbnailRow", m_pReplayItemManager );
  588. m_vecRows.AddToTail( pNewRow );
  589. InvalidateLayout();
  590. }
  591. // Need another thumbnail in this row?
  592. if ( iColumn >= m_vecRows[iRow]->GetNumReplayItems() )
  593. {
  594. // Hit the max number of thumbnails in this row?
  595. if ( iColumn >= m_nReplayThumbnailsPerRow )
  596. break;
  597. m_vecRows[iRow]->AddReplayThumbnail( REPLAY_HANDLE_INVALID );
  598. }
  599. if ( iReplayIndex >= m_vecReplays.Count() )
  600. {
  601. m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetReplayItem( REPLAY_HANDLE_INVALID );
  602. m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetVisible( false );
  603. }
  604. else
  605. {
  606. m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetReplayItem( m_vecReplays[iReplayIndex] );
  607. m_vecRows[iRow]->m_vecThumbnails[iColumn]->SetVisible( true );
  608. }
  609. }
  610. // Update the button counts
  611. wchar_t wszTemp[256];
  612. wchar_t wszCount[10];
  613. int iNextReplays = clamp( m_vecReplays.Count() - iFirstReplayOnPage - iThumbnailsPerPage, 0, iThumbnailsPerPage );
  614. if ( iNextReplays > 0 )
  615. {
  616. _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", iNextReplays );
  617. g_pVGuiLocalize->ConstructString_safe( wszTemp, g_pVGuiLocalize->Find("#Replay_NextX"), 1, wszCount );
  618. SetDialogVariable( "nextbuttontext", wszTemp );
  619. m_pShowNextButton->SetVisible( true );
  620. }
  621. else
  622. {
  623. m_pShowNextButton->SetVisible( false );
  624. }
  625. int iPrevReplays = clamp( iFirstReplayOnPage, 0, iThumbnailsPerPage );
  626. if ( iPrevReplays > 0 )
  627. {
  628. _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", iPrevReplays );
  629. g_pVGuiLocalize->ConstructString_safe( wszTemp, g_pVGuiLocalize->Find("#Replay_PrevX"), 1, wszCount );
  630. SetDialogVariable( "prevbuttontext", wszTemp );
  631. m_pShowPrevButton->SetVisible( true );
  632. }
  633. else
  634. {
  635. m_pShowPrevButton->SetVisible( false );
  636. }
  637. RemoveEmptyRows();
  638. // We may have changed our size, so we need to tell our parent that it should re-layout
  639. m_pParentListPanel->InvalidateLayout();
  640. }
  641. void CBaseThumbnailCollection::ApplySchemeSettings( vgui::IScheme *pScheme )
  642. {
  643. BaseClass::ApplySchemeSettings( pScheme );
  644. LoadControlSettings( "resource/ui/replaybrowser/thumbnailcollection.res", "GAME" );
  645. m_pCaratLabel = dynamic_cast< CExLabel * >( FindChildByName( "CaratLabel" ) );
  646. m_pTitleLabel = dynamic_cast< CExLabel * >( FindChildByName( "TitleLabel" ) );
  647. m_pNoReplayItemsLabel = dynamic_cast< CExLabel * >( FindChildByName( "NoReplayItemsLabel" ) );
  648. m_pShowNextButton = dynamic_cast< CExButton * >( FindChildByName( "ShowNextButton" ) );
  649. if ( m_pShowNextButton )
  650. {
  651. m_pShowNextButton->AddActionSignalTarget( this );
  652. }
  653. m_pShowPrevButton = dynamic_cast< CExButton * >( FindChildByName( "ShowPrevButton" ) );
  654. if ( m_pShowPrevButton )
  655. {
  656. m_pShowPrevButton->AddActionSignalTarget( this );
  657. }
  658. UpdateViewingPage();
  659. }
  660. void CBaseThumbnailCollection::PerformLayout()
  661. {
  662. int nUnconvertedBgWidth = GetWide();
  663. // Layout no replay items label
  664. m_pNoReplayItemsLabel->SetPos( ( GetWide() - m_pNoReplayItemsLabel->GetWide() ) / 2, YRES( 40 ) );
  665. m_pNoReplayItemsLabel->SetVisible( !m_pReplayItemManager->GetItemCount() );
  666. m_pNoReplayItemsLabel->SetContentAlignment( Label::a_center );
  667. int nStartY = YRES(5);
  668. // Update the title count
  669. wchar_t wszCount[10];
  670. _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_vecReplays.Count() );
  671. wchar_t wszTemp[256];
  672. const char *pszLocString = IsMovieCollection() ? "#Replay_SavedMovies" : "#Replay_UnrenderedReplays";
  673. g_pVGuiLocalize->ConstructString_safe( wszTemp, g_pVGuiLocalize->Find(pszLocString), 1, wszCount );
  674. SetDialogVariable( "titleandcount", wszTemp );
  675. // Setup labels for unconverted replay display
  676. LayoutUpperPanels( nStartY, nUnconvertedBgWidth );
  677. int nCurrentY = GetRowStartY();
  678. // Position our prev button
  679. int nButtonX = (GetWide() - m_pShowPrevButton->GetWide()) * 0.5;
  680. if ( m_pShowPrevButton )
  681. {
  682. m_pShowPrevButton->SetPos( nButtonX, nCurrentY - m_pShowPrevButton->GetTall() - YRES(2) );
  683. nCurrentY += YRES(2);
  684. }
  685. // Setup converted row positions
  686. for ( int i = 0; i < m_vecRows.Count(); ++i )
  687. {
  688. CReplayBrowserThumbnailRow *pRow = m_vecRows[ i ];
  689. pRow->SetPos( m_nStartX, nCurrentY );
  690. pRow->InvalidateLayout( true, true );
  691. int nRowTall = pRow->GetTall();
  692. nCurrentY += nRowTall;
  693. }
  694. int nTotalHeight = nCurrentY;
  695. // Position our next button
  696. if ( m_pShowNextButton )
  697. {
  698. m_pShowNextButton->SetPos( nButtonX, nCurrentY );
  699. bool bMakeRoomForNextPrev = (m_pShowPrevButton && m_pShowNextButton && (m_pShowPrevButton->IsVisible() || m_pShowNextButton->IsVisible()));
  700. if ( bMakeRoomForNextPrev )
  701. {
  702. nTotalHeight += m_pShowNextButton->GetTall() + YRES(5);
  703. }
  704. }
  705. LayoutBackgroundPanel( nUnconvertedBgWidth, nTotalHeight );
  706. // TODO: Resizing can cause an InvalidateLayout() call if the new & old dimensions differ,
  707. // perhaps calling this here is not the best idea.
  708. // Adjust total height of panel
  709. SetTall( nTotalHeight );
  710. BaseClass::PerformLayout();
  711. }
  712. //-----------------------------------------------------------------------------
  713. CReplayThumbnailCollection::CReplayThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager )
  714. : BaseClass( pParent, pName, pReplayItemManager ),
  715. m_pWarningLabel( NULL ),
  716. m_pUnconvertedBg( NULL )
  717. {
  718. // Create additional panels for unconverted rows
  719. m_pLinePanel = new Panel( this, "Line" );
  720. m_pWarningLabel = new CExLabel( this, "WarningLabel", "#Replay_ConversionWarning" );
  721. m_pUnconvertedBg = new Panel( this, "UnconvertedBg" );
  722. m_pRenderAllButton = new CExButton( this, "RenderAllButton", "#Replay_RenderAll" );
  723. }
  724. bool CReplayThumbnailCollection::IsMovieCollection() const
  725. {
  726. return false;
  727. }
  728. void CReplayThumbnailCollection::PerformLayout()
  729. {
  730. BaseClass::PerformLayout();
  731. }
  732. void CReplayThumbnailCollection::ApplySchemeSettings( IScheme *pScheme )
  733. {
  734. BaseClass::ApplySchemeSettings( pScheme );
  735. #if defined( TF_CLIENT_DLL )
  736. if ( m_pUnconvertedBg )
  737. {
  738. vgui::IBorder *pBorder = pScheme->GetBorder( "ButtonBorder" );
  739. m_pUnconvertedBg->SetBorder( pBorder );
  740. SetPaintBorderEnabled( true );
  741. }
  742. #else
  743. SetPaintBorderEnabled( false );
  744. #endif
  745. // Get current key binding for "save_replay", if any.
  746. const char *pBoundKey = engine->Key_LookupBinding( "save_replay" );
  747. if ( !pBoundKey || FStrEq( pBoundKey, "(null)" ) )
  748. {
  749. m_pNoReplayItemsLabel->SetText( "#Replay_NoKeyBoundNoReplays" );
  750. }
  751. else
  752. {
  753. char szKey[16];
  754. Q_snprintf( szKey, sizeof(szKey), "%s", pBoundKey );
  755. wchar_t wKey[16];
  756. wchar_t wLabel[256];
  757. g_pVGuiLocalize->ConvertANSIToUnicode( szKey, wKey, sizeof( wKey ) );
  758. g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#Replay_NoReplays" ), 1, wKey );
  759. m_pNoReplayItemsLabel->SetText( wLabel );
  760. }
  761. }
  762. void CReplayThumbnailCollection::LayoutUpperPanels( int nStartY, int nBgWidth )
  763. {
  764. int nUnconvertedY = nStartY + 2 * REPLAY_BUFFER_HEIGHT;
  765. if ( !m_pTitleLabel )
  766. return;
  767. m_pTitleLabel->SizeToContents();
  768. m_pTitleLabel->SetBounds( m_nStartX, nUnconvertedY, GetWide(), m_pTitleLabel->GetTall() );
  769. m_pTitleLabel->SetVisible( true );
  770. int cx, cy;
  771. int nWarningStartY = nUnconvertedY + m_pTitleLabel->GetTall() + REPLAY_BUFFER_HEIGHT;
  772. m_pWarningLabel->GetContentSize( cx, cy );
  773. m_pWarningLabel->SetBounds( m_nStartX, nWarningStartY, 2 * GetWide() / 3, cy ); // For when "convert all" button is shown
  774. m_pWarningLabel->SetVisible( m_pReplayItemManager->GetItemCount() > 0 );
  775. // Setup carat label
  776. if ( m_pCaratLabel )
  777. {
  778. m_pCaratLabel->SizeToContents();
  779. m_pCaratLabel->SetPos( m_nStartX - m_pCaratLabel->GetWide() - XRES(3), nUnconvertedY );
  780. m_pCaratLabel->SetVisible( true );
  781. }
  782. // Setup line
  783. int nLineBuffer = m_pReplayItemManager->GetItemCount() > 0 ? YRES( 15 ) : nStartY;
  784. int nLineY = nWarningStartY + nLineBuffer;
  785. m_pLinePanel->SetBounds( 0, nLineY, nBgWidth, 1 );
  786. m_pLinePanel->SetVisible( true );
  787. int nButtonX = (GetWide() - m_pShowPrevButton->GetWide()) * 0.5;
  788. if ( m_pShowPrevButton )
  789. {
  790. m_pShowPrevButton->SetPos( nButtonX, nLineY );
  791. }
  792. }
  793. void CReplayThumbnailCollection::LayoutBackgroundPanel( int nWide, int nTall )
  794. {
  795. // Setup bounds for dark background, if there are unconverted replays in this collection
  796. if ( m_pUnconvertedBg )
  797. {
  798. m_pUnconvertedBg->SetBounds(
  799. 0,
  800. 0,
  801. nWide,
  802. nTall
  803. );
  804. m_pUnconvertedBg->SetVisible( true );
  805. // Setup convert all button
  806. int nWarningLabelX, nWarningLabelY;
  807. m_pWarningLabel->GetPos( nWarningLabelX, nWarningLabelY );
  808. int nRenderAllX = m_pUnconvertedBg->GetWide() - m_pRenderAllButton->GetWide() - XRES( 5 );
  809. m_pRenderAllButton->SetPos( nRenderAllX, nWarningLabelY - m_pRenderAllButton->GetTall()/2 );
  810. m_pRenderAllButton->SetVisible( m_pReplayItemManager->GetItemCount() > 0 );
  811. }
  812. }
  813. Panel *CReplayThumbnailCollection::GetLowestPanel( int &nVerticalBuffer )
  814. {
  815. nVerticalBuffer = YRES( 8 );
  816. return m_pLinePanel;
  817. }
  818. //-----------------------------------------------------------------------------
  819. CMovieThumbnailCollection::CMovieThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager,
  820. int nDay, int nMonth, int nYear, bool bShowSavedMoviesLabel )
  821. : BaseClass( pParent, pName, pReplayItemManager )
  822. {
  823. Init( nDay, nMonth, nYear, bShowSavedMoviesLabel );
  824. }
  825. CMovieThumbnailCollection::CMovieThumbnailCollection( CReplayListPanel *pParent, const char *pName, IReplayItemManager *pReplayItemManager, bool bShowSavedMoviesLabel )
  826. : BaseClass( pParent, pName, pReplayItemManager )
  827. {
  828. Init( -1, -1, -1, bShowSavedMoviesLabel );
  829. }
  830. void CMovieThumbnailCollection::Init( int nDay, int nMonth, int nYear, bool bShowSavedMoviesLabel )
  831. {
  832. m_nDay = nDay;
  833. m_nMonth = nMonth;
  834. m_nYear = nYear;
  835. if ( m_nDay == OLDER_MOVIES_COLLECTION )
  836. {
  837. m_pDateLabel = new CExLabel( this, "DateLabel", "#Replay_OlderMovies" );
  838. }
  839. else
  840. {
  841. m_pDateLabel = m_nDay >= 0 ? new CExLabel( this, "DateLabel", CReplayTime::GetLocalizedDate( g_pVGuiLocalize, nDay, nMonth, nYear ) ) : NULL;
  842. }
  843. m_bShowSavedMoviesLabel = bShowSavedMoviesLabel;
  844. }
  845. bool CMovieThumbnailCollection::IsMovieCollection() const
  846. {
  847. return true;
  848. }
  849. void CMovieThumbnailCollection::PerformLayout()
  850. {
  851. BaseClass::PerformLayout();
  852. // Movies have multiple collections under a single header. So we use the total movies, not the amount in this collection.
  853. if ( g_pReplayMovieManager )
  854. {
  855. wchar_t wszCount[10];
  856. _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", g_pReplayMovieManager->GetMovieCount() );
  857. wchar_t wszTemp[256];
  858. g_pVGuiLocalize->ConstructString_safe( wszTemp, g_pVGuiLocalize->Find("#Replay_SavedMovies"), 1, wszCount );
  859. SetDialogVariable( "titleandcount", wszTemp );
  860. }
  861. if ( m_pDateLabel )
  862. {
  863. m_pDateLabel->SetVisible( m_pReplayItemManager->GetItemCount() );
  864. }
  865. }
  866. void CMovieThumbnailCollection::ApplySchemeSettings( IScheme *pScheme )
  867. {
  868. BaseClass::ApplySchemeSettings( pScheme );
  869. if ( m_pDateLabel )
  870. {
  871. m_pDateLabel->SetVisible( true );
  872. }
  873. if ( m_pCaratLabel )
  874. {
  875. m_pCaratLabel->SetVisible( m_bShowSavedMoviesLabel );
  876. }
  877. if ( m_pTitleLabel )
  878. {
  879. m_pTitleLabel->SetVisible( m_bShowSavedMoviesLabel );
  880. }
  881. m_pNoReplayItemsLabel->SetText( "#Replay_NoMovies" );
  882. }
  883. void CMovieThumbnailCollection::LayoutUpperPanels( int nStartY, int nBgWidth )
  884. {
  885. if ( m_pTitleLabel && m_pTitleLabel->IsVisible() )
  886. {
  887. m_pTitleLabel->SizeToContents();
  888. m_pTitleLabel->SetPos( m_nStartX, nStartY );
  889. m_pCaratLabel->SizeToContents();
  890. m_pCaratLabel->SetPos( m_nStartX - m_pCaratLabel->GetWide() - XRES(3), nStartY + ( m_pCaratLabel->GetTall() - m_pCaratLabel->GetTall() ) / 2 );
  891. nStartY += m_pTitleLabel->GetTall() + YRES( 5 );
  892. }
  893. // Date label
  894. if ( m_pDateLabel && m_pDateLabel->IsVisible() )
  895. {
  896. m_pDateLabel->SizeToContents();
  897. m_pDateLabel->SetPos( m_nStartX, nStartY );
  898. }
  899. }
  900. Panel *CMovieThumbnailCollection::GetLowestPanel( int &nVerticalBuffer )
  901. {
  902. nVerticalBuffer = YRES( 8 );
  903. return m_pDateLabel ? m_pDateLabel : m_pTitleLabel;
  904. }
  905. bool CMovieThumbnailCollection::DoesDateMatch( int nDay, int nMonth, int nYear )
  906. {
  907. return ( nDay == m_nDay ) && ( nMonth == m_nMonth ) && ( nYear == m_nYear );
  908. }
  909. #endif