using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RecrownedAthenaeum.Camera;
using System;
using System.Diagnostics;
namespace RecrownedAthenaeum.ScreenSystem
{
///
/// Called when the first screen is being shown.
///
/// The screen to show after the loading screen.
public delegate void ShowFirstScreen(Screen screen);
///
/// Custom spritebatch begin call.
///
///
public delegate void BeginBatch(SpriteBatch spriteBatch);
///
/// 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 ShowFirstScreen ShowFirstScreenEvent;
///
/// The function to call that begins the batch.
///
public BeginBatch beginBatchFunc;
private GraphicsDeviceManager graphics;
private Screen previousScreen;
private RenderTarget2D previousScreenRenderTarget;
private Screen currentScreen;
private Camera2D camera;
private bool firstScreenChangeComplete;
private bool resizing;
///
/// Currently displayed screen.
///
public Screen Screen
{
get
{
return currentScreen;
}
set
{
previousScreen = currentScreen;
currentScreen = value;
if (!Screen.Initiated)
{
Screen.Initiate(new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight), camera);
}
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 manage multiple screens and their transitions.
///
/// The camera to be used to perform the correct translations and transformations. Will use default set in if left null.
/// The graphics device manager to be used. Will use default set in if left null.
/// The function to call to begin a batch to be used generally. Will use the built-in one in screen manager if not provided.
public ScreenManager(Camera2D camera = null, GraphicsDeviceManager graphicsDeviceManager = null, BeginBatch beginBatchFunction = null)
{
if (beginBatchFunction == null) beginBatchFunction = BasicBeginBatch;
if (camera == null) camera = Configuration.Camera2D;
if (graphicsDeviceManager == null) graphicsDeviceManager = Configuration.GraphicsDeviceManager;
graphics = graphicsDeviceManager ?? throw new ArgumentNullException("Graphics device manager argument cannot be null if setup's graphics device manager is also null.");
this.camera = camera ?? throw new ArgumentNullException("2d camera argument cannot be null if setup's 2d camera is also null.");
beginBatchFunc = beginBatchFunction;
}
///
/// 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)
{
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;
OnFirstScreenChange(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.
///
/// Uses this batch to render.
public void DrawCurrentScreen(SpriteBatch spriteBatch)
{
graphics.GraphicsDevice.Clear(Screen.BackgroundColor);
if (Screen.State == ScreenState.EnterTransition && previousScreen != null && previousScreen.UseRenderTargetForExitTransition)
{
graphics.GraphicsDevice.SetRenderTarget(previousScreenRenderTarget);
graphics.GraphicsDevice.Clear(previousScreen.BackgroundColor);
beginBatchFunc(spriteBatch);
previousScreen.Draw(spriteBatch);
spriteBatch.End();
graphics.GraphicsDevice.SetRenderTarget(null);
Screen.UpdatePreviousScreenFrame(previousScreenRenderTarget);
}
beginBatchFunc(spriteBatch);
Screen.Draw(spriteBatch);
spriteBatch.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;
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 OnFirstScreenChange(Screen screen)
{
ShowFirstScreenEvent?.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) return;
if (disposing)
{
previousScreenRenderTarget?.Dispose();
}
disposing = true;
}
///
/// Destructor.
///
~ScreenManager()
{
Dispose(false);
}
private void BasicBeginBatch(SpriteBatch spriteBatch)
{
spriteBatch.Begin(effect: camera.BasicEffect, blendState: BlendState.NonPremultiplied);
}
}
}