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); } } }