using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using RecrownedAthenaeum.Graphics.Render; using System; using System.Diagnostics; namespace RecrownedAthenaeum.UI.ScreenSystem { /// /// Called when the first screen is being shown. /// /// The screen to show after the loading screen. public delegate void NeedNextScreen(Screen screen); /// /// A manager for screens. Helps with transitions and updating screens as well as resizes. /// public class ScreenManager : IDisposable { bool disposed = false; /// /// Called when the first loading screen is done, and needs to show the landing screen. /// public event NeedNextScreen NeedNextScreen; /// /// The settings this manager will use to begin a sprite batch. /// private GraphicsDeviceManager graphics; private Screen previousScreen; private RenderTarget2D previousScreenRenderTarget; private Screen currentScreen; private bool firstScreenChangeComplete; private bool resizing; /// /// Currently displayed screen. /// public Screen Screen { get { return currentScreen; } set { previousScreen = currentScreen; currentScreen = value; if (Screen.width != graphics.PreferredBackBufferWidth || Screen.height != graphics.PreferredBackBufferHeight) { Screen.ApplySize(graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); } if (previousScreen != null && previousScreenRenderTarget == null && previousScreen.UseRenderTargetForExitTransition) { previousScreenRenderTarget = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); } graphics.GraphicsDevice.SetRenderTarget(previousScreenRenderTarget); graphics.GraphicsDevice.Clear(Color.Black); Debug.WriteLine("Showing " + value.GetType().Name); Screen.Show(); previousScreen?.Hide(); } } /// /// Creates a screen manager that helps update, draw, and manage multiple screens and their transitions. Uses its own . /// /// The graphics device manager to be used. public ScreenManager(GraphicsDeviceManager graphicsDeviceManager) { graphics = graphicsDeviceManager ?? throw new ArgumentNullException("Graphics device manager argument cannot be null."); } /// /// Updates the screens. Should be called once every frame. /// /// Contains the time that has passed from the last frame. /// Whether or not there is something a transition should be waiting for. Usually used to wait for assets to complete loading. public void UpdateCurrentScreen(GameTime gameTime, bool waiting) { waiting = !waiting; switch (Screen.State) { case ScreenState.EnterTransition: if (previousScreen != null && previousScreen.UseRenderTargetForExitTransition) { previousScreen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, waiting); } Screen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, waiting); break; case ScreenState.ExitTransition: if (Screen.UseRenderTargetForExitTransition || Screen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, waiting)) { if (!firstScreenChangeComplete) { firstScreenChangeComplete = true; OnNeedNextScreen(Screen); } if (Screen.NextScreen != null) { Debug.WriteLine("Changing to the next given screen."); Screen = Screen.NextScreen; } else if (previousScreen != null) { Debug.WriteLine("Changing to previous screen."); Screen = previousScreen; } else { throw new InvalidOperationException("No next screen provided and no previous screen to return to."); } } break; } Screen.Update(gameTime); } /// /// Renders screen into window. /// Starts and ends the given . /// /// The consistent sprite batch to use. Needs to be consistent as changing render targets requires ending and beginning the sprite batch. public void DrawScreen(ConsistentSpriteBatch consistentSpriteBatch) { if (consistentSpriteBatch == null) { throw new ArgumentNullException(nameof(consistentSpriteBatch)); } if (Screen == null) return; graphics.GraphicsDevice.Clear(Screen.BackgroundColor); if (Screen.State == ScreenState.EnterTransition && previousScreen != null && previousScreen.UseRenderTargetForExitTransition) { graphics.GraphicsDevice.SetRenderTarget(previousScreenRenderTarget); graphics.GraphicsDevice.Clear(previousScreen.BackgroundColor); consistentSpriteBatch.Begin(); previousScreen.Draw(consistentSpriteBatch); consistentSpriteBatch.End(); graphics.GraphicsDevice.SetRenderTarget(null); Screen.UpdatePreviousScreenFrame(previousScreenRenderTarget); } consistentSpriteBatch.Begin(); Screen.Draw(consistentSpriteBatch); consistentSpriteBatch.End(); } /// /// Should be called when resize is occurring to change to a loading screen. /// This will notify the screen of the status of the assets, change the screen to a loading screen, and dispose of the previous screen buffer. /// /// The loading screen to change to. public void Resize(LoadingScreen loadingScreen) { if (resizing) throw new InvalidOperationException("Already resizing."); resizing = true; if (Screen != null) { Screen.AssetLoadStateChange(false); Screen = loadingScreen; previousScreenRenderTarget.Dispose(); previousScreenRenderTarget = null; } } /// /// Notifies all screen that assets have completed being loaded after a resize. /// public void PostResize() { if (!resizing) throw new InvalidOperationException("Was never resizing."); Screen.AssetLoadStateChange(true); } private void OnNeedNextScreen(Screen screen) { NeedNextScreen?.Invoke(screen); } /// /// Disposes this. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// An overridable dispose. /// /// True of user invoked dispose called. public virtual void Dispose(bool disposing) { if (disposed) throw new ObjectDisposedException(GetType().Name); if (disposing) { previousScreenRenderTarget?.Dispose(); } disposed = true; } /// /// Destructor. /// ~ScreenManager() { Dispose(false); } } }