using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Screens.Transitions;
namespace MonoGame.Extended.Screens;
///
/// Manages a stack of instances in a game.
///
///
///
/// Screens are managed using a stack based approach where the topmost screen is considered the active screen,
/// while underlying screens can continue to update and draw in the background based on their settings.
///
///
/// Note: Developers should be aware of performance considerations when keeping multiple screens active in the
/// background for updates and/or drawing.
///
///
public class ScreenManager : SimpleDrawableGameComponent
{
private readonly Stack _screens;
private Screen _activeScreen;
private Transition _activeTransition;
private Screen[] _cachedScreens;
private bool _isScreenArrayDirty;
///
/// Gets the currently active screen at the top of the screen stack,
/// or if no screens are loaded.
///
public Screen ActiveScreen => _activeScreen;
///
/// Gets a read-only list of all screens in the stack, ordered from bottom to top.
///
///
/// For performance, the list is cached internally and only rebuilt when the screen stack has been modified.
///
public IReadOnlyList Screens
{
get
{
if (_isScreenArrayDirty)
{
_cachedScreens = _screens.Reverse().ToArray();
_isScreenArrayDirty = false;
}
return _cachedScreens;
}
}
///
/// Initializes a new instance of the class.
///
public ScreenManager()
{
_screens = new Stack();
_cachedScreens = Array.Empty();
_isScreenArrayDirty = true;
}
///
/// Pushes a new screen onto the stack and makes it the active screen.
///
///
/// The previous active screen remains in the screen stack but becomes inactive.
///
/// The screen to show.
/// Thrown when is .
public void ShowScreen(Screen screen)
{
ArgumentNullException.ThrowIfNull(screen);
if (_activeScreen != null)
{
_activeScreen.IsActive = false;
}
screen.ScreenManager = this;
screen.IsActive = true;
screen.Initialize();
screen.LoadContent();
_screens.Push(screen);
_activeScreen = screen;
_isScreenArrayDirty = true;
}
///
/// Pushes a new screen onto the stack with a transition effect and makes it the active screen.
///
///
/// The previous active screen remains in the screen stack but becomes inactive.
///
/// The screen to show.
/// The transition effect to use when showing the screen.
public void ShowScreen(Screen screen, Transition transition)
{
if (_activeTransition != null)
{
return;
}
_activeTransition = transition;
_activeTransition.StateChanged += (sender, args) => ShowScreen(screen);
_activeTransition.Completed += (sender, args) =>
{
_activeTransition.Dispose();
_activeTransition = null;
};
}
///
/// Pops the current active screen from the stack, disposing it and making the next screen active.
///
///
/// If the screen stack becomes empty, no screen will be active.
///
public void CloseScreen()
{
if (!_screens.TryPop(out Screen screen))
{
return;
}
screen.IsActive = false;
screen.UnloadContent();
screen.Dispose();
_isScreenArrayDirty = true;
if (_screens.TryPeek(out _activeScreen))
{
_activeScreen.IsActive = true;
}
}
///
/// Pops the current active screen from the stack with a transition effect, disposing it and making the next screen active.
///
///
/// If the screen stack becomes empty, no screen will be active.
///
/// The transition effect to use when closing the screen.
public void CloseScreen(Transition transition)
{
if (_activeTransition != null)
{
return;
}
_activeTransition = transition;
_activeTransition.StateChanged += (sender, args) => CloseScreen();
_activeTransition.Completed += (sender, args) =>
{
_activeTransition.Dispose();
_activeTransition = null;
};
}
///
/// Replaces the current active screen with a new screen by closing the current screen and showing the new one.
///
///
/// This is equivalent to calling followed by
///
/// The screen to replace the current active screen with.
public void ReplaceScreen(Screen screen)
{
CloseScreen();
ShowScreen(screen);
}
///
/// Replaces the current active screen with a new screen using a transition effect.
///
///
/// This is equivalent to calling followed by .
///
/// The screen to replace the current active screen with.
/// The transition effect to use when replacing the screen.
public void ReplaceScreen(Screen screen, Transition transition)
{
if (_activeTransition != null)
{
return;
}
_activeTransition = transition;
_activeTransition.StateChanged += (sender, args) => ReplaceScreen(screen);
_activeTransition.Completed += (sender, args) =>
{
_activeTransition.Dispose();
_activeTransition = null;
};
}
///
/// Loads a screen, replacing any existing screens.
///
/// The screen to load
[Obsolete("This method is provided for backward compatibility and will be removed in the next major release. For new code, use ShowScreen(Screen), CloseScreen(), or ReplaceScreen(Screen).")]
public void LoadScreen(Screen screen)
{
ReplaceScreen(screen);
}
///
/// Loads a screen with a transition effect, replacing any existing screens.
///
/// The screen to load
/// The transition effect to use.
[Obsolete("This method is provided for backward compatibility and will be removed in the next major release. For new code, use ShowScreen(Screen), CloseScreen(), or ReplaceScreen(Screen).")]
public void LoadScreen(Screen screen, Transition transition)
{
ReplaceScreen(screen, transition);
}
///
/// Clears all screens from the stack, disposing them and setting no active screen.
///
public void ClearScreens()
{
while (_screens.TryPop(out Screen screen))
{
screen.IsActive = false;
screen.UnloadContent();
screen.Dispose();
}
_activeScreen = null;
_isScreenArrayDirty = true;
}
///
/// Initializes the screen manager and the currently active screen if one exists.
///
public override void Initialize()
{
base.Initialize();
if (_activeScreen != null)
{
_activeScreen.Initialize();
}
}
///
/// Loads content for the screen manager and the currently active screen if one exists.
///
protected override void LoadContent()
{
base.LoadContent();
if (_activeScreen != null)
{
_activeScreen.LoadContent();
}
}
///
/// Unloads content for the screen manager and the currently active screen if one exists.
///
protected override void UnloadContent()
{
base.UnloadContent();
if (_activeScreen != null)
{
_activeScreen.UnloadContent();
}
}
///
/// Updates all screens in the stack where the or
/// property is set to .
///
///
/// Update order for screens is done from the bottom of the stack to the top of.
///
/// Provides a snapshot of timing values.
public override void Update(GameTime gameTime)
{
IReadOnlyList screens = Screens;
for (int i = 0; i < screens.Count; i++)
{
Screen screen = screens[i];
if (screen.IsActive || screen.UpdateWhenInactive)
{
screen.Update(gameTime);
}
}
if (_activeTransition != null)
{
_activeTransition.Update(gameTime);
}
}
///
/// Draws all screens in the stack where the or
/// property is set to .
///
///
/// Draw order for screens is done from the bottom of the stack to the top to allow background screens
/// to draw before foreground ones for property visual stacking.
///
/// Provides a snapshot of timing values.
public override void Draw(GameTime gameTime)
{
IReadOnlyList screens = Screens;
for (int i = 0; i < screens.Count; i++)
{
Screen screen = screens[i];
if (screen.IsActive || screen.DrawWhenInactive)
{
screen.Draw(gameTime);
}
}
if (_activeTransition != null)
{
_activeTransition.Draw(gameTime);
}
}
}