#include "cbase.h" #include #include #include "subtitlepanel.h" #include "filesystem.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" using namespace vgui; #define MAX_CHEAP_LINES 10 ConVar cheap_captions_test( "cheap_captions_test", "0", 0 ); ConVar cheap_captions_fadetime( "cheap_captions_fadetime", "0.5", 0 ); //----------------------------------------------------------------------------- // Purpose: Determines if we should be playing with captions //----------------------------------------------------------------------------- bool ShouldUseCaptioning() { if ( cheap_captions_test.GetBool() ) { return true; } extern ConVar closecaption; return closecaption.GetBool(); } CCaptionSequencer::CCaptionSequencer() : m_bCaptions( false ) { Reset(); } void CCaptionSequencer::Reset() { // captioning start when rendering stable, not simply at movie start m_CaptionStartTime = 0; m_flPauseTime = 0; m_flTotalPauseTime = 0; m_bPaused = false; // initial priming state to fetch a caption m_bShowingCaption = false; m_bCaptionStale = true; m_CurCaptionString[0] = '\0'; m_CurCaptionStartTime = 0.0f; m_CurCaptionEndTime = 0.0f; m_CurCaptionColor = 0xFFFFFFFF; if ( m_CaptionBuf.TellPut() ) { // reset to start m_CaptionBuf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); } else { m_CaptionBuf.Purge(); } } bool CCaptionSequencer::Init( const char *pFilename ) { Reset(); m_bCaptions = false; if ( g_pFullFileSystem->ReadFile( pFilename, "GAME", m_CaptionBuf ) ) { m_bCaptions = true; } return m_bCaptions; } void CCaptionSequencer::SetStartTime( float flStarTtime ) { // Start our captions now m_CaptionStartTime = flStarTtime; } void CCaptionSequencer::Pause( bool bPause ) { if ( m_bPaused == bPause ) return; m_bPaused = bPause; if ( !m_bPaused ) { // determine elapsed time paused m_flTotalPauseTime += Plat_FloatTime() - m_flPauseTime; } else { // stop the clock m_flPauseTime = Plat_FloatTime(); } } float CCaptionSequencer::GetElapsedTime() { float flElapsed; if ( !m_bPaused ) { flElapsed = Plat_FloatTime() - m_flTotalPauseTime - m_CaptionStartTime; } else { // hold the clock flElapsed = m_flPauseTime - m_flTotalPauseTime - m_CaptionStartTime; } return flElapsed; } bool CCaptionSequencer::GetCaptionToken( char *token, int tokenLen ) { if ( !token || !tokenLen ) return false; if ( !m_CaptionBuf.IsValid() ) { // end of data return false; } m_CaptionBuf.GetLine( token, tokenLen ); char *pCRLF = V_stristr( token, "\r" ); if ( pCRLF ) { *pCRLF = '\0'; } m_CaptionBuf.SeekGet( CUtlBuffer::SEEK_CURRENT, 1 ); return true; } bool CCaptionSequencer::GetNextCaption( void ) { char buff[MAX_CAPTION_LENGTH]; if ( !GetCaptionToken( m_CurCaptionString, sizeof( m_CurCaptionString ) ) ) { // end of captions m_CurCaptionString[0] = '\0'; return false; } // hex color GetCaptionToken( buff, sizeof( buff ) ); sscanf( buff, "%x", &m_CurCaptionColor ); // float start time GetCaptionToken( buff, sizeof( buff ) ); m_CurCaptionStartTime = atof( buff ); // float end time GetCaptionToken( buff, sizeof( buff ) ); m_CurCaptionEndTime = atof( buff ); // have valid caption m_bCaptionStale = false; return true; } const char *CCaptionSequencer::GetCurrentCaption( int *pColorOut ) { if ( !m_bCaptions ) { return NULL; } if ( m_CaptionStartTime ) { // get a timeline float elapsed = GetElapsedTime(); // Get a new caption because we've just finished one if ( !m_bShowingCaption && m_bCaptionStale ) { GetNextCaption(); } if ( m_bShowingCaption ) { if ( elapsed > m_CurCaptionEndTime ) // Caption just turned off { m_bShowingCaption = false; // Don't draw caption m_bCaptionStale = true; // Trigger getting a new one on the next frame } } else { if ( elapsed > m_CurCaptionStartTime ) // Turn Caption on { m_bShowingCaption = true; } } if ( m_bShowingCaption && m_CurCaptionString[0] ) { if ( pColorOut ) { *pColorOut = m_CurCaptionColor; } return m_CurCaptionString; } } return NULL; } float CCaptionSequencer::GetAlpha() { if ( !m_bShowingCaption || m_bPaused ) return 0; float flElapsed = GetElapsedTime(); float flAlpha = RemapValClamped( flElapsed, m_CurCaptionStartTime, m_CurCaptionStartTime + cheap_captions_fadetime.GetFloat(), 0.0f, 255.0f ); flAlpha = RemapValClamped( flElapsed, m_CurCaptionEndTime - cheap_captions_fadetime.GetFloat(), m_CurCaptionEndTime, flAlpha, 0.0f ); return flAlpha; } CSubtitlePanel::CSubtitlePanel( vgui::Panel *pParent, const char *pCaptionFile, int nPlaybackHeight ) : vgui::Panel( pParent, "SubtitlePanel" ) { SetScheme( "basemodui_scheme" ); SetProportional( true ); int nParentWide = pParent->GetWide(); int nParentTall = pParent->GetTall(); SetBounds( 0, 0, nParentWide, nParentTall ); SetPaintBackgroundEnabled( true ); m_hFont = vgui::INVALID_FONT; vgui::HScheme hScheme = vgui::scheme()->GetScheme( "basemodui_scheme" ); vgui::IScheme *pNewScheme = vgui::scheme()->GetIScheme( hScheme ); if ( pNewScheme ) { m_hFont = pNewScheme->GetFont( IsGameConsole() ? "CloseCaption_Console" : "CloseCaption_Normal", true ); } m_nFontTall = vgui::surface()->GetFontTall( m_hFont ); m_pSubtitleLabel = new vgui::Label( this, "SubtitleLabel", L"" ); m_pSubtitleLabel->SetFont( m_hFont ); int nWidth = nParentWide * 0.60f; int xPos = ( nParentWide - nWidth ) / 2; // assume video is centered // must be scaled according to playback height, due to letterboxing // don't want to cut into or overlap border, need to be within video, and title safe // so pushes up according to font height int yOffset = ( nPlaybackHeight - nParentTall )/2; int yPos = ( 0.85f * nPlaybackHeight ) - yOffset; // captions are anchored to a baseline and grow upward // any resolution changes then are title safe // tall enough for 10 lines m_pSubtitleLabel->SetPos( xPos, yPos - m_nFontTall * MAX_CHEAP_LINES ); m_pSubtitleLabel->SetTall( m_nFontTall * MAX_CHEAP_LINES ); m_pSubtitleLabel->SetWide( nWidth ); m_pSubtitleLabel->SetContentAlignment( vgui::Label::a_south ); m_pSubtitleLabel->SetWrap( true ); const char *pFixedCaptionFile = pCaptionFile; char captionFilename[MAX_PATH]; if ( !V_stristr( pCaptionFile, ".txt" ) ) { // Strip any possible extension, add on the '_captions.txt' ending V_StripExtension( pCaptionFile, captionFilename, MAX_PATH ); V_strncat( captionFilename, "_captions.txt", MAX_PATH ); pFixedCaptionFile = captionFilename; } // Setup our captions m_bHasCaptions = m_Captions.Init( pFixedCaptionFile ); // prevent any thinking or drawing when captions absent SetVisible( m_bHasCaptions ); } bool CSubtitlePanel::StartCaptions() { if ( !m_bHasCaptions ) return false; m_Captions.SetStartTime( Plat_FloatTime() ); return true; } void CSubtitlePanel::Pause( bool bPause ) { m_Captions.Pause( bPause ); } bool CSubtitlePanel::HasCaptions() { return m_bHasCaptions; } void CSubtitlePanel::OnThink() { int nColor = 0xFFFFFFFF; const char *pCaptionText = m_Captions.GetCurrentCaption( &nColor ); m_pSubtitleLabel->SetText( pCaptionText ); SetAlpha( ShouldUseCaptioning() ? m_Captions.GetAlpha() : 0 ); if ( pCaptionText ) { int r = ( nColor >> 24 ) & 0xFF; int g = ( nColor >> 16 ) & 0xFF; int b = ( nColor >> 8 ) & 0xFF; int a = ( nColor >> 0 ) & 0xFF; m_pSubtitleLabel->SetFgColor( Color( r, g, b, a ) ); } } void CSubtitlePanel::PaintBackground() { int nMsgWide, nMsgTall; m_pSubtitleLabel->GetContentSize( nMsgWide, nMsgTall ); if ( !nMsgWide || !nMsgTall ) { return; } int nLabelX, nLabelY, nLabelWide, nLabelTall; m_pSubtitleLabel->GetBounds( nLabelX, nLabelY, nLabelWide, nLabelTall ); // widen box to seat label better int nBoxWide = nLabelWide + GetWide() * 0.05f; int nBoxTall = nMsgTall + m_nFontTall; // center horizontally int nBoxX = ( GetWide() - nBoxWide )/2; // determine the top line of the south anchored text and center int nBoxY = ( nLabelY + nLabelTall ) - nMsgTall/2 - nBoxTall/2; DrawBox( nBoxX, nBoxY, nBoxWide, nBoxTall, Color( 0, 0, 0, 150 ), 1.0f ); }