Files
itgmania212121/src/ScreenManager.cpp
T

940 lines
28 KiB
C++
Raw Normal View History

2011-03-17 01:47:30 -04:00
/*
* We maintain a pool of "prepared" screens, which are screens previously loaded
* to be available on demand.
*
* When a screen pops off the stack that's in g_setPersistantScreens, it goes to
* the prepared list. If that screen is used again before it's deleted, it'll be
* reused.
*
* Typically, the first screen in a group will prepare all of the nearby screens it
* may load. When it loads one, the prepared screen will be used.
*
* A group of screens may only want to preload some screens, and not others,
* while still considering those screens part of the same group. For example,
* an attract loop typically has several very lightweight screens and a few
* expensive screens. Preloading the lightweight screens can improve responsiveness,
* but preloading the expensive screens may use too much memory and take too long
* to load all at once. By calling GroupScreen(), entering these screens will not
* trigger cleanup.
*
* Example uses:
* - ScreenOptions1 preloads ScreenOptions2, and persists both. Moving from Options1
* and Options2 and back is instant and reuses both.
* - ScreenMenu groups and persists itself and ScreenSubmenu. ScreenSubmenu
* is not preloaded, so it will be loaded on demand the first time it's used,
* but will remain loaded if it returns to ScreenMenu.
* - ScreenSelectMusic groups itself and ScreenPlayerOptions, and persists only
* ScreenSelectMusic. This will cause SSMusic will be loaded once, and SPO to
* be loaded on demand.
* - ScreenAttract1 preloads and persists ScreenAttract1, 3, 5 and 7, and groups 1
* through 7. 1, 3, 5 and 7 will remain in memory; the rest will be loaded on
* demand.
*
* If a screen is added to the screen stack that isn't in the current screen group
* (added to by GroupScreen), the screen group is reset: all prepared screens are
* unloaded and the persistance list is cleared.
*
* Note that not all screens yet support reuse in this way; proper use of BeginScreen
* is required. This will misbehave if a screen pushes another screen that's already
* in use lower on the stack, but that's not useful; it would allow infinite screen
* recursion.
*
* SM_GainFocus/SM_LoseFocus: These are sent to screens when they become the
* topmost screen, or stop being the topmost screen.
*
* A few subtleties:
*
* With delayed screens (eg. ScreenGameplay being pre-loaded by ScreenStage), SM_GainFocus
* isn't sent until the loaded screen actually is activated (put on the stack).
*
* With normal screen loads, the new screen is loaded before the old screen is destroyed.
* This means that the old dtor is called *after* the new ctor. If some global properties
* (eg. GAMESTATE) are being unset by the old screen's destructor, and set by the new
* screen's constructor, they'll happen in the wrong order. Use SM_GainFocus and
* SM_LoseFocus, instead.
*
* SM_LoseFocus is always sent after SM_GainFocus, and vice-versa: you can't gain focus
* if you already have it, and you can't lose focus if you don't have it.
*/
#include "global.h"
#include "ScreenManager.h"
#include "Preference.h"
#include "RageLog.h"
#include "RageUtil.h"
#include "GameSoundManager.h"
#include "RageDisplay.h"
#include "SongManager.h"
#include "RageTextureManager.h"
#include "ThemeManager.h"
#include "FontManager.h"
#include "Screen.h"
#include "ScreenDimensions.h"
#include "Foreach.h"
#include "ActorUtil.h"
ScreenManager* SCREENMAN = NULL; // global and accessable from anywhere in our program
static Preference<bool> g_bDelayedScreenLoad( "DelayedScreenLoad", false );
//static Preference<bool> g_bPruneFonts( "PruneFonts", true );
// Screen registration
static map<RString,CreateScreenFn> *g_pmapRegistrees = NULL;
/** @brief Utility functions for the ScreenManager. */
namespace ScreenManagerUtil
{
// in draw order first to last
struct LoadedScreen
{
Screen *m_pScreen;
/* Normally true. If false, the screen is owned by another screen
* and was given to us for use, and it's not ours to free. */
bool m_bDeleteWhenDone;
ScreenMessage m_SendOnPop;
LoadedScreen()
{
m_pScreen = NULL;
m_bDeleteWhenDone = true;
m_SendOnPop = SM_None;
}
};
Actor *g_pSharedBGA; // BGA object that's persistent between screens
RString m_sPreviousTopScreen;
vector<LoadedScreen> g_ScreenStack; // bottommost to topmost
vector<Screen*> g_OverlayScreens;
set<RString> g_setGroupedScreens;
set<RString> g_setPersistantScreens;
vector<LoadedScreen> g_vPreparedScreens;
vector<Actor*> g_vPreparedBackgrounds;
// Add a screen to g_ScreenStack. This is the only function that adds to g_ScreenStack.
void PushLoadedScreen( const LoadedScreen &ls )
{
LOG->Trace( "PushScreen: \"%s\"", ls.m_pScreen->GetName().c_str() );
LOG->MapLog( "ScreenManager::TopScreen", "Top Screen: %s", ls.m_pScreen->GetName().c_str() );
// Be sure to push the screen first, so GetTopScreen returns the screen
// during BeginScreen.
g_ScreenStack.push_back( ls );
// Set the name of the loading screen.
{
LuaThreadVariable var1( "PreviousScreen", m_sPreviousTopScreen );
LuaThreadVariable var2( "LoadingScreen", ls.m_pScreen->GetName() );
ls.m_pScreen->BeginScreen();
}
// If this is the new top screen, save the name.
if( g_ScreenStack.size() == 1 )
m_sPreviousTopScreen = ls.m_pScreen->GetName();
SCREENMAN->RefreshCreditsMessages();
SCREENMAN->PostMessageToTopScreen( SM_GainFocus, 0 );
}
bool ScreenIsPrepped( const RString &sScreenName )
{
FOREACH( LoadedScreen, g_vPreparedScreens, s )
{
if( s->m_pScreen->GetName() == sScreenName )
return true;
}
return false;
}
/* If the named screen is loaded, remove it from the prepared list and
* return it in ls. */
bool GetPreppedScreen( const RString &sScreenName, LoadedScreen &ls )
{
FOREACH( LoadedScreen, g_vPreparedScreens, s )
{
if( s->m_pScreen->GetName() == sScreenName )
{
ls = *s;
g_vPreparedScreens.erase( s );
return true;
}
}
return false;
}
void BeforeDeleteScreen()
{
// Deleting a screen can take enough time to cause a frame skip.
SCREENMAN->ZeroNextUpdate();
}
/* If we're deleting a screen, it's probably releasing texture and other
* resources, so trigger cleanups. */
void AfterDeleteScreen()
{
/* Now that we've actually deleted a screen, it makes sense to clear out
* cached textures. */
TEXTUREMAN->DeleteCachedTextures();
/* Cleanup song data. This can free up a fair bit of memory, so do it
* after deleting screens. */
SONGMAN->Cleanup();
}
/* Take ownership of all screens and backgrounds that are owned by
* us (this excludes screens where m_bDeleteWhenDone is false).
* Clear the prepared lists. The contents of apOut must be
* freed by the caller. */
void GrabPreparedActors( vector<Actor*> &apOut )
{
FOREACH( LoadedScreen, g_vPreparedScreens, s )
if( s->m_bDeleteWhenDone )
apOut.push_back( s->m_pScreen );
g_vPreparedScreens.clear();
FOREACH( Actor*, g_vPreparedBackgrounds, a )
apOut.push_back( *a );
g_vPreparedBackgrounds.clear();
g_setGroupedScreens.clear();
g_setPersistantScreens.clear();
}
/* Called when changing screen groups. Delete all prepared screens,
* reset the screen group and list of persistant screens. */
void DeletePreparedScreens()
{
vector<Actor*> apActorsToDelete;
GrabPreparedActors( apActorsToDelete );
BeforeDeleteScreen();
FOREACH( Actor*, apActorsToDelete, a )
SAFE_DELETE( *a );
AfterDeleteScreen();
}
};
using namespace ScreenManagerUtil;
RegisterScreenClass::RegisterScreenClass( const RString& sClassName, CreateScreenFn pfn )
{
if( g_pmapRegistrees == NULL )
g_pmapRegistrees = new map<RString,CreateScreenFn>;
map<RString,CreateScreenFn>::iterator iter = g_pmapRegistrees->find( sClassName );
ASSERT_M( iter == g_pmapRegistrees->end(), ssprintf("Screen class '%s' already registered.", sClassName.c_str()) );
(*g_pmapRegistrees)[sClassName] = pfn;
}
ScreenManager::ScreenManager()
{
// Register with Lua.
{
Lua *L = LUA->Get();
lua_pushstring( L, "SCREENMAN" );
this->PushSelf( L );
lua_settable( L, LUA_GLOBALSINDEX );
LUA->Release( L );
}
g_pSharedBGA = new Actor;
m_bZeroNextUpdate = false;
m_PopTopScreen = SM_Invalid;
m_OnDonePreparingScreen = SM_Invalid;
}
ScreenManager::~ScreenManager()
{
LOG->Trace( "ScreenManager::~ScreenManager()" );
LOG->UnmapLog( "ScreenManager::TopScreen" );
SAFE_DELETE( g_pSharedBGA );
for( unsigned i=0; i<g_ScreenStack.size(); i++ )
{
if( g_ScreenStack[i].m_bDeleteWhenDone )
SAFE_DELETE( g_ScreenStack[i].m_pScreen );
}
g_ScreenStack.clear();
DeletePreparedScreens();
for( unsigned i=0; i<g_OverlayScreens.size(); i++ )
SAFE_DELETE( g_OverlayScreens[i] );
g_OverlayScreens.clear();
// Unregister with Lua.
LUA->UnsetGlobal( "SCREENMAN" );
}
// This is called when we start up, and when the theme changes or is reloaded.
void ScreenManager::ThemeChanged()
{
LOG->Trace( "ScreenManager::ThemeChanged" );
// reload common sounds
m_soundStart.Load( THEME->GetPathS("Common","start") );
m_soundCoin.Load( THEME->GetPathS("Common","coin"), true );
m_soundCancel.Load( THEME->GetPathS("Common","cancel"), true );
m_soundInvalid.Load( THEME->GetPathS("Common","invalid") );
m_soundScreenshot.Load( THEME->GetPathS("Common","screenshot") );
// unload overlay screens
for( unsigned i=0; i<g_OverlayScreens.size(); i++ )
SAFE_DELETE( g_OverlayScreens[i] );
g_OverlayScreens.clear();
// reload overlay screens
RString sOverlays = THEME->GetMetric( "Common","OverlayScreens" );
vector<RString> asOverlays;
split( sOverlays, ",", asOverlays );
for( unsigned i=0; i<asOverlays.size(); i++ )
{
Screen *pScreen = MakeNewScreen( asOverlays[i] );
LuaThreadVariable var2( "LoadingScreen", pScreen->GetName() );
pScreen->BeginScreen();
g_OverlayScreens.push_back( pScreen );
}
// reload song manager colors (to avoid crashes) -aj
SONGMAN->ResetGroupColors();
// force recreate of new BGA
SAFE_DELETE( g_pSharedBGA );
g_pSharedBGA = new Actor;
this->RefreshCreditsMessages();
}
void ScreenManager::ReloadOverlayScreens()
{
// unload overlay screens
for( unsigned i=0; i<g_OverlayScreens.size(); i++ )
SAFE_DELETE( g_OverlayScreens[i] );
g_OverlayScreens.clear();
// reload overlay screens
RString sOverlays = THEME->GetMetric( "Common","OverlayScreens" );
vector<RString> asOverlays;
split( sOverlays, ",", asOverlays );
for( unsigned i=0; i<asOverlays.size(); i++ )
{
Screen *pScreen = MakeNewScreen( asOverlays[i] );
LuaThreadVariable var2( "LoadingScreen", pScreen->GetName() );
pScreen->BeginScreen();
g_OverlayScreens.push_back( pScreen );
}
this->RefreshCreditsMessages();
}
Screen *ScreenManager::GetTopScreen()
{
if( g_ScreenStack.empty() )
return NULL;
return g_ScreenStack[g_ScreenStack.size()-1].m_pScreen;
}
Screen *ScreenManager::GetScreen( int iPosition )
{
if( iPosition >= (int) g_ScreenStack.size() )
return NULL;
return g_ScreenStack[iPosition].m_pScreen;
}
bool ScreenManager::AllowOperatorMenuButton() const
{
FOREACH( LoadedScreen, g_ScreenStack, s )
{
if( !s->m_pScreen->AllowOperatorMenuButton() )
return false;
}
return true;
}
bool ScreenManager::IsStackedScreen( const Screen *pScreen ) const
{
// True if the screen is in the screen stack, but not the first.
for( unsigned i = 1; i < g_ScreenStack.size(); ++i )
if( g_ScreenStack[i].m_pScreen == pScreen )
return true;
return false;
}
/* Pop the top screen off the stack, sending SM_LoseFocus messages and
* returning the message the popped screen wants sent to the new top
* screen. Does not send SM_GainFocus. */
ScreenMessage ScreenManager::PopTopScreenInternal( bool bSendLoseFocus )
{
if( g_ScreenStack.empty() )
return SM_None;
LoadedScreen ls = g_ScreenStack.back();
g_ScreenStack.erase( g_ScreenStack.end()-1, g_ScreenStack.end() );
if( bSendLoseFocus )
ls.m_pScreen->HandleScreenMessage( SM_LoseFocus );
ls.m_pScreen->EndScreen();
if( g_setPersistantScreens.find(ls.m_pScreen->GetName()) != g_setPersistantScreens.end() )
{
// Move the screen back to the prepared list.
g_vPreparedScreens.push_back( ls );
}
else
{
if( ls.m_bDeleteWhenDone )
{
BeforeDeleteScreen();
SAFE_DELETE( ls.m_pScreen );
AfterDeleteScreen();
}
}
if( g_ScreenStack.size() )
LOG->MapLog( "ScreenManager::TopScreen", "Top Screen: %s", g_ScreenStack.back().m_pScreen->GetName().c_str() );
else
LOG->UnmapLog( "ScreenManager::TopScreen" );
return ls.m_SendOnPop;
}
void ScreenManager::Update( float fDeltaTime )
{
// Pop the top screen, if PopTopScreen was called.
if( m_PopTopScreen != SM_Invalid )
{
ScreenMessage SM = m_PopTopScreen;
m_PopTopScreen = SM_Invalid;
ScreenMessage SM2 = PopTopScreenInternal();
SendMessageToTopScreen( SM_GainFocus );
SendMessageToTopScreen( SM );
SendMessageToTopScreen( SM2 );
}
/* Screens take some time to load. If we don't do this, then screens
* receive an initial update that includes all of the time they spent
* loading, which will chop off their tweens.
*
* We don't want to simply cap update times; for example, the stage
* screen sets a 4 second timer, preps the gameplay screen, and then
* displays the prepped screen after the timer runs out; this lets the
* load time be masked (as long as the load takes less than 4 seconds).
* If we cap that large update delta from the screen load, the update
* to load the new screen will come after 4 seconds plus the load time.
*
* So, let's just zero the first update for every screen. */
ASSERT( !g_ScreenStack.empty() || m_sDelayedScreen != "" ); // Why play the game if there is nothing showing?
Screen* pScreen = g_ScreenStack.empty() ? NULL : GetTopScreen();
bool bFirstUpdate = pScreen && pScreen->IsFirstUpdate();
/* Loading a new screen can take seconds and cause a big jump on the new
* Screen's first update. Clamp the first update delta so that the
* animations don't jump. */
if( pScreen && m_bZeroNextUpdate )
{
LOG->Trace( "Zeroing this update. Was %f", fDeltaTime );
fDeltaTime = 0;
m_bZeroNextUpdate = false;
}
// Update screens.
{
for( unsigned i=0; i<g_ScreenStack.size(); i++ )
g_ScreenStack[i].m_pScreen->Update( fDeltaTime );
g_pSharedBGA->Update( fDeltaTime );
for( unsigned i=0; i<g_OverlayScreens.size(); i++ )
g_OverlayScreens[i]->Update( fDeltaTime );
}
/* The music may be started on the first update. If we're reading from a CD,
* it might not start immediately. Make sure we start playing the sound before
* continuing, since it's strange to start rendering before the music starts. */
if( bFirstUpdate )
SOUND->Flush();
/* If we're currently inside a background screen load, and m_sDelayedScreen
* is set, then the screen called SetNewScreen before we finished preparing.
* Postpone it until we're finished loading. */
if( m_sDelayedScreen.size() != 0 )
{
LoadDelayedScreen();
}
}
void ScreenManager::Draw()
{
/* If it hasn't been updated yet, skip the render. We can't call Update(0), since
* that'll confuse the "zero out the next update after loading a screen logic.
* If we don't render, don't call BeginFrame or EndFrame. That way, we won't
* clear the buffer, and we won't wait for vsync. */
if( g_ScreenStack.size() && g_ScreenStack.back().m_pScreen->IsFirstUpdate() )
return;
if( !DISPLAY->BeginFrame() )
return;
DISPLAY->CameraPushMatrix();
DISPLAY->LoadMenuPerspective( 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_CENTER_X, SCREEN_CENTER_Y );
g_pSharedBGA->Draw();
DISPLAY->CameraPopMatrix();
for( unsigned i=0; i<g_ScreenStack.size(); i++ ) // Draw all screens bottom to top
g_ScreenStack[i].m_pScreen->Draw();
for( unsigned i=0; i<g_OverlayScreens.size(); i++ )
g_OverlayScreens[i]->Draw();
DISPLAY->EndFrame();
}
void ScreenManager::Input( const InputEventPlus &input )
{
// LOG->Trace( "ScreenManager::Input( %d-%d, %d-%d, %d-%d, %d-%d )",
// DeviceI.device, DeviceI.button, GameI.controller, GameI.button, MenuI.player, MenuI.button, StyleI.player, StyleI.col );
// First, give overlay screens a shot at the input. If OverlayInput returns
// true, it handled the input, so don't pass it further. OverlayInput could
// probably be merged with Input, but that would require changing all Input
// overloads, as well as all MenuLeft, etc. overloads.
for( unsigned i = 0; i < g_OverlayScreens.size(); ++i )
{
Screen *pScreen = g_OverlayScreens[i];
if( pScreen->OverlayInput(input) )
return;
}
// Pass input to the topmost screen. If we have a new top screen pending, don't
// send to the old screen, but do send to overlay screens.
if( m_sDelayedScreen != "" )
return;
if( g_ScreenStack.empty() )
return;
g_ScreenStack.back().m_pScreen->Input( input );
}
// Just create a new screen; don't do any associated cleanup.
Screen* ScreenManager::MakeNewScreen( const RString &sScreenName )
{
RageTimer t;
LOG->Trace( "Loading screen: \"%s\"", sScreenName.c_str() );
RString sClassName = THEME->GetMetric( sScreenName,"Class" );
map<RString,CreateScreenFn>::iterator iter = g_pmapRegistrees->find( sClassName );
if( iter == g_pmapRegistrees->end() )
RageException::Throw( "Screen \"%s\" has an invalid class \"%s\".", sScreenName.c_str(), sClassName.c_str() );
this->ZeroNextUpdate();
CreateScreenFn pfn = iter->second;
Screen *ret = pfn( sScreenName );
LOG->Trace( "Loaded \"%s\" (\"%s\") in %f", sScreenName.c_str(), sClassName.c_str(), t.GetDeltaTime() );
return ret;
}
void ScreenManager::PrepareScreen( const RString &sScreenName )
{
// If the screen is already prepared, stop.
if( ScreenIsPrepped(sScreenName) )
return;
Screen* pNewScreen = MakeNewScreen(sScreenName);
{
LoadedScreen ls;
ls.m_pScreen = pNewScreen;
g_vPreparedScreens.push_back( ls );
}
/* Don't delete previously prepared versions of the screen's background,
* and only prepare it if it's different than the current background
* and not already loaded. */
RString sNewBGA = THEME->GetPathB(sScreenName,"background");
if( !sNewBGA.empty() && sNewBGA != g_pSharedBGA->GetName() )
{
Actor *pNewBGA = NULL;
FOREACH( Actor*, g_vPreparedBackgrounds, a )
{
if( (*a)->GetName() == sNewBGA )
{
pNewBGA = *a;
break;
}
}
// Create the new background before deleting the previous so that we keep
// any common textures loaded.
if( pNewBGA == NULL )
{
LOG->Trace( "Loading screen background \"%s\"", sNewBGA.c_str() );
Actor *pActor = ActorUtil::MakeActor( sNewBGA );
pActor->SetName( sNewBGA );
g_vPreparedBackgrounds.push_back( pActor );
}
}
// Prune any unused fonts now that we have had a chance to reference the fonts
/*
if(g_bPruneFonts) {
FONT->PruneFonts();
}
*/
TEXTUREMAN->DiagnosticOutput();
}
void ScreenManager::GroupScreen( const RString &sScreenName )
{
g_setGroupedScreens.insert( sScreenName );
}
void ScreenManager::PersistantScreen( const RString &sScreenName )
{
g_setPersistantScreens.insert( sScreenName );
}
void ScreenManager::SetNewScreen( const RString &sScreenName )
{
ASSERT( sScreenName != "" );
m_sDelayedScreen = sScreenName;
}
/* Activate the screen and/or its background, if either are loaded.
* Return true if both were activated. */
bool ScreenManager::ActivatePreparedScreenAndBackground( const RString &sScreenName )
{
bool bLoadedBoth = true;
// Find the prepped screen.
if( GetTopScreen() == NULL || GetTopScreen()->GetName() != sScreenName )
{
LoadedScreen ls;
if( !GetPreppedScreen(sScreenName, ls) )
{
bLoadedBoth = false;
}
else
{
PushLoadedScreen( ls );
}
}
// Find the prepared shared background (if any), and activate it.
RString sNewBGA = THEME->GetPathB(sScreenName,"background");
if( sNewBGA != g_pSharedBGA->GetName() )
{
Actor *pNewBGA = NULL;
if( sNewBGA.empty() )
{
pNewBGA = new Actor;
}
else
{
FOREACH( Actor*, g_vPreparedBackgrounds, a )
{
if( (*a)->GetName() == sNewBGA )
{
pNewBGA = *a;
g_vPreparedBackgrounds.erase( a );
break;
}
}
}
/* If the BGA isn't loaded yet, load a dummy actor. If we're not going to use the same
* BGA for the new screen, always move the old BGA back to g_vPreparedBackgrounds now. */
if( pNewBGA == NULL )
{
bLoadedBoth = false;
pNewBGA = new Actor;
}
/* Move the old background back to the prepared list, or delete it if
* it's a blank actor. */
if( g_pSharedBGA->GetName() == "" )
delete g_pSharedBGA;
else
g_vPreparedBackgrounds.push_back( g_pSharedBGA );
g_pSharedBGA = pNewBGA;
g_pSharedBGA->PlayCommand( "On" );
}
return bLoadedBoth;
}
void ScreenManager::LoadDelayedScreen()
{
RString sScreenName = m_sDelayedScreen;
m_sDelayedScreen = "";
// Pop the top screen, if any.
ScreenMessage SM = PopTopScreenInternal();
/* If the screen is already prepared, activate it before performing any
* cleanup, so it doesn't get deleted by cleanup. */
bool bLoaded = ActivatePreparedScreenAndBackground( sScreenName );
vector<Actor*> apActorsToDelete;
if( g_setGroupedScreens.find(sScreenName) == g_setGroupedScreens.end() )
{
/* It's time to delete all old prepared screens. Depending on
* DelayedScreenLoad, we can either delete the screens before or after
* we load the new screen. Either way, we must remove them from the
* prepared list before we prepare new screens.
* If DelayedScreenLoad is true, delete them now; this lowers memory
* requirements, but results in redundant loads as we unload common data. */
if( g_bDelayedScreenLoad )
DeletePreparedScreens();
else
GrabPreparedActors( apActorsToDelete );
}
// If the screen wasn't already prepared, load it.
if( !bLoaded )
{
PrepareScreen( sScreenName );
// Screens may not call SetNewScreen from the ctor or Init(). (We don't do this
// check inside PrepareScreen; that may be called from a thread for concurrent
// loading, and the main thread may call SetNewScreen during that time.)
ASSERT_M( m_sDelayedScreen.empty(), m_sDelayedScreen );
bLoaded = ActivatePreparedScreenAndBackground( sScreenName );
ASSERT( bLoaded );
}
if( !apActorsToDelete.empty() )
{
BeforeDeleteScreen();
FOREACH( Actor*, apActorsToDelete, a )
SAFE_DELETE( *a );
AfterDeleteScreen();
}
MESSAGEMAN->Broadcast( Message_ScreenChanged );
SendMessageToTopScreen( SM );
}
void ScreenManager::AddNewScreenToTop( const RString &sScreenName, ScreenMessage SendOnPop )
{
// Load the screen, if it's not already prepared.
PrepareScreen( sScreenName );
// Find the prepped screen.
LoadedScreen ls;
bool b = GetPreppedScreen( sScreenName, ls );
ASSERT( b );
ls.m_SendOnPop = SendOnPop;
if( g_ScreenStack.size() )
g_ScreenStack.back().m_pScreen->HandleScreenMessage( SM_LoseFocus );
PushLoadedScreen( ls );
}
void ScreenManager::PopTopScreen( ScreenMessage SM )
{
ASSERT( g_ScreenStack.size() > 0 );
m_PopTopScreen = SM;
}
/* Clear the screen stack; only used before major, unusual state changes,
* such as resetting the game or jumping to the service menu. Don't call
* from inside a screen. */
void ScreenManager::PopAllScreens()
{
// Make sure only the top screen receives LoseFocus.
bool bFirst = true;
while( !g_ScreenStack.empty() )
{
PopTopScreenInternal( bFirst );
bFirst = false;
}
DeletePreparedScreens();
}
void ScreenManager::PostMessageToTopScreen( ScreenMessage SM, float fDelay )
{
Screen* pTopScreen = GetTopScreen();
if( pTopScreen != NULL )
pTopScreen->PostScreenMessage( SM, fDelay );
}
void ScreenManager::SendMessageToTopScreen( ScreenMessage SM )
{
Screen* pTopScreen = GetTopScreen();
if( pTopScreen != NULL )
pTopScreen->HandleScreenMessage( SM );
}
void ScreenManager::SystemMessage( const RString &sMessage )
{
LOG->Trace( "%s", sMessage.c_str() );
Message msg( "SystemMessage" );
msg.SetParam( "Message", sMessage );
msg.SetParam( "NoAnimate", false );
MESSAGEMAN->Broadcast( msg );
}
void ScreenManager::SystemMessageNoAnimate( const RString &sMessage )
{
// LOG->Trace( "%s", sMessage.c_str() ); // don't log because the caller is likely calling us every frame
Message msg( "SystemMessage" );
msg.SetParam( "Message", sMessage );
msg.SetParam( "NoAnimate", true );
MESSAGEMAN->Broadcast( msg );
}
void ScreenManager::HideSystemMessage()
{
MESSAGEMAN->Broadcast( "HideSystemMessage" );
}
void ScreenManager::RefreshCreditsMessages()
{
MESSAGEMAN->Broadcast( "RefreshCreditText" );
}
void ScreenManager::ZeroNextUpdate()
{
m_bZeroNextUpdate = true;
/* Loading probably took a little while. Let's reset stats. This prevents us
* from displaying an unnaturally low FPS value, and the next FPS value we
* display will be accurate, which makes skips in the initial tween-ins more
* apparent. */
DISPLAY->ResetStats();
}
void ScreenManager::PlayInvalidSound()
{
RageSoundParams p;
p.m_bIsCriticalSound = true;
m_soundInvalid.Play( &p );
}
/* Always play these sounds, even if we're in a silent attract loop. */
void ScreenManager::PlayStartSound()
{
RageSoundParams p;
p.m_bIsCriticalSound = true;
m_soundStart.Play( &p );
}
void ScreenManager::PlayCoinSound()
{
RageSoundParams p;
p.m_bIsCriticalSound = true;
m_soundCoin.Play( &p );
}
void ScreenManager::PlayCancelSound()
{
RageSoundParams p;
p.m_bIsCriticalSound = true;
m_soundCancel.Play( &p );
}
void ScreenManager::PlayScreenshotSound()
{
RageSoundParams p;
p.m_bIsCriticalSound = true;
m_soundScreenshot.Play( &p );
}
void ScreenManager::PlaySharedBackgroundOffCommand()
{
g_pSharedBGA->PlayCommand("Off");
}
// lua start
#include "LuaBinding.h"
/** @brief Allow Lua to have access to the ScreenManager. */
class LunaScreenManager: public Luna<ScreenManager>
{
public:
// Note: PrepareScreen binding is not allowed; loading data inside
// Lua causes the Lua lock to be held for the duration of the load,
// which blocks concurrent rendering
static int SetNewScreen( T* p, lua_State *L ) { p->SetNewScreen( SArg(1) ); return 0; }
static int GetTopScreen( T* p, lua_State *L )
{
Actor *pScreen = p->GetTopScreen();
if( pScreen != NULL )
pScreen->PushSelf(L);
else
lua_pushnil( L );
return 1;
}
static int SystemMessage( T* p, lua_State *L ) { p->SystemMessage( SArg(1) ); return 0; }
static int ScreenIsPrepped( T* p, lua_State *L ) { lua_pushboolean( L, ScreenManagerUtil::ScreenIsPrepped( SArg(1) ) ); return 1; }
static int ScreenClassExists( T* p, lua_State *L ) { lua_pushboolean( L, g_pmapRegistrees->find( SArg(1) ) != g_pmapRegistrees->end() ); return 1; }
static int AddNewScreenToTop( T* p, lua_State *L ) { p->AddNewScreenToTop( SArg(1) ); return 0; }
//static int GetScreenStackSize( T* p, lua_State *L ) { lua_pushnumber( L, ScreenManagerUtil::g_ScreenStack.size() ); return 1; }
static int ReloadOverlayScreens( T* p, lua_State *L ) { p->ReloadOverlayScreens(); return 0; }
LunaScreenManager()
{
ADD_METHOD( SetNewScreen );
ADD_METHOD( GetTopScreen );
ADD_METHOD( SystemMessage );
ADD_METHOD( ScreenIsPrepped );
ADD_METHOD( ScreenClassExists );
ADD_METHOD( AddNewScreenToTop );
//ADD_METHOD( GetScreenStackSize );
ADD_METHOD( ReloadOverlayScreens );
}
};
LUA_REGISTER_CLASS( ScreenManager )
// lua end
/*
* (c) 2001-2003 Chris Danford, Glenn Maynard
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/