#region Copyright
/*
--------------------------------------------------------------------------------
This source file is part of Xenocide
by Project Xenocide Team
For the latest info on Xenocide, see http://www.projectxenocide.com/
This work is licensed under the Creative Commons
Attribution-NonCommercial-ShareAlike 2.5 License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-nc-sa/2.5/
or send a letter to Creative Commons, 543 Howard Street, 5th Floor,
San Francisco, California, 94105, USA.
--------------------------------------------------------------------------------
*/
/*
* @file ScreenManager.cs
* @date Created: 2007/01/20
* @author File creator: dteviot
* @author Credits: none
*/
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using CeGui.Renderers.Xna;
using ProjectXenocide.UI.Dialogs;
using ProjectXenocide.Model;
using ProjectXenocide.Model.Geoscape;
using ProjectXenocide.Utils;
#endregion
namespace ProjectXenocide.UI.Screens
{
///
/// This class is responsible for keeping track of the Screen currently
/// being shown to the user.
///
public class ScreenManager : IDisposable
{
///
/// Set the screen to show on the next update()
///
/// The new screen to show
///
/// This will destroy any screen currently being shown
/// Need to delay swapping the screen until the next update.
/// Due to issues with any dialogs being shown being "owned" by the screen
/// currently being shown.
///
public void ScheduleScreen(Screen newScreen)
{
// we can only have one screen state change planed
Debug.Assert((null == nextScreen) && (null == pushScreen) && !popScreen);
nextScreen = newScreen;
}
/// Put a new screen at the top of the draw order
/// The new screen to show
///
/// The screen currently being shown can be recovered by PopScreen()
/// Need to delay swapping the screen until the next update.
/// Due to issues with any dialogs being shown being "owned" by the screen
/// currently being shown.
///
public void PushScreen(Screen newScreen)
{
// we can only have one screen state change planed
Debug.Assert((null == nextScreen) && (null == pushScreen) && !popScreen);
Debug.Assert(0 < screenStack.Count);
pushScreen = newScreen;
}
/// Signal to destroy topmost screen in display order and replace with next one down
///
/// The screen currently being shown can be recovered by PopScreen()
/// Need to delay swapping the screen until the next update.
/// Due to issues with any dialogs being shown being "owned" by the screen
/// currently being shown.
///
public void PopScreen()
{
// we can only have one screen state change planed
Debug.Assert((null == nextScreen) && (null == pushScreen) && !popScreen);
Debug.Assert(0 < screenStack.Count);
popScreen = true;
}
///
/// Replace the screen currently being shown with the "nextScreen"
///
private void SwapScreens()
{
Debug.Assert((null != nextScreen) ^ (null != pushScreen) ^ popScreen);
// if replacing or popping, dispose of currently showing screen, else just hide the current screen
if ((null != nextScreen) || popScreen)
{
if (0 < screenStack.Count)
{
Screen screen = screenStack.Peek();
screen.SaveState();
screen.UnloadContent();
screen.Dispose();
screenStack.Pop();
}
}
else
{
if (0 < screenStack.Count)
{
screenStack.Peek().Visible = false;
}
}
// if popping, just unhide old screen, otherwise we need to create new screen
if (popScreen)
{
screenStack.Peek().Visible = true;
}
else
{
// and set up new screen
Screen screen = (null != nextScreen) ? nextScreen : pushScreen;
screenStack.Push(screen);
screen.LoadContent(content, Xenocide.Instance.GraphicsDevice);
screen.Show();
}
// clear state change flags
Util.GeoTimeDebugWriteLine("Showing Screen {0}", screenStack.Peek().GetType().Name);
popScreen = false;
nextScreen = null;
pushScreen = null;
}
// set this to exit the game
private bool quitGame;
///
/// set this to exit the game
///
public bool QuitGame
{
get { return quitGame; }
set { if (value) quitGame = true; }
}
///
/// Load the Screen's graphic content
///
/// the display
public void LoadContent(GraphicsDevice device)
{
// construct the content manager, if it doesn't already exist
if (content == null)
{
content = new ContentManager(Xenocide.Instance.Services);
}
foreach(Screen screen in screenStack)
{
screen.LoadContent(content, device);
}
}
///
/// Unload's the Scene's graphic content
///
public void UnloadContent()
{
if (content != null)
{
content.Unload();
}
foreach (Screen screen in screenStack)
{
screen.UnloadContent();
}
}
///
/// Update any model data
///
/// Provides a snapshot of timing values.
public void Update(GameTime gameTime)
{
// if there is a dialog queued for display, and we're not showing any dialogs
// show the dialog
if ((0 == showingDialogs.Count) && (0 < queuedDialogs.Count))
{
ShowDialog(queuedDialogs[0]);
queuedDialogs.RemoveAt(0);
}
// only update the screen when there are no dialogs
if (0 == showingDialogs.Count)
{
// if we're scheduled to swap the screen, do so now
if ((null != nextScreen) || (null != pushScreen) || popScreen)
{
SwapScreens();
}
else if (0 < screenStack.Count)
{
// pump current screen
// unless game is running slow, in which case skip the update
// we've probably been loading resources or something like that, and they don't count
Debug.Assert(Xenocide.Instance.IsFixedTimeStep);
if (!gameTime.IsRunningSlowly && (gameTime.ElapsedRealTime.TotalMilliseconds < 20))
{
screenStack.Peek().Update(gameTime);
}
}
}
}
///
/// Render the 3D scene
///
/// Provides a snapshot of timing values.
/// Device to use for render
public void Draw(GameTime gameTime, GraphicsDevice device)
{
if (0 < screenStack.Count)
{
screenStack.Peek().Draw(gameTime, device);
fpsCalcs();
}
}
///
/// Put dialog into the queue to be displayed
///
/// The dialog to queue
public void QueueDialog(Dialog dialog)
{
//ToDo: put dialog into queue in priority position
queuedDialogs.Add(dialog);
}
///
/// Put dialog into top of stack of dialogs being shown
///
/// The dialog to show
public void ShowDialog(Dialog dialog)
{
Util.GeoTimeDebugWriteLine("Showing dialog {0}", dialog.GetType().Name);
// get the screen to remember its state
// Ugly, Ugly hack.
// Really, if it's the geoscape, remember the current camera position.
// so if Dialog spawns a new Geoscape, it will have same camera position
// (prevents the geoscape jumping around)
screenStack.Peek().SaveState();
// disable controls on current topmost dialog/screen
TopmostFrame.Enable(false);
// and now put this dialog on the screen
showingDialogs.Push(dialog);
dialog.Show();
}
///
/// Remove the topmost dialog currently being shown
///
/// The dialog making the call (which should ALSO be the topmost dialog)
public void CloseDialog(Dialog dialog)
{
Util.GeoTimeDebugWriteLine("Closing dialog {0}", dialog.GetType().Name);
Debug.Assert(dialog == showingDialogs.Peek());
// remove dialog
showingDialogs.Pop().Dispose();
// re-enable whatever dialog/screen is now topmost
TopmostFrame.Enable(true);
}
///
/// This function is intended for checking data integrity
///
/// asserts if there are dialogs queued or showing
[Conditional("DEBUG")]
public void AssertNoDialogsQueuedOrShowing()
{
Debug.Assert((0 == showingDialogs.Count) && (0 == queuedDialogs.Count));
}
///
/// Get the "topmost" dialog or screen on the display
///
/// Topmost Frame
public Frame TopmostFrame
{
get
{
if (0 < showingDialogs.Count)
{
return showingDialogs.Peek();
}
else
{
return (0 == screenStack.Count) ? null : screenStack.Peek();
}
}
}
///
/// Implement IDisposable
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Implement IDisposable
///
/// false when called from a finalizer
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (content != null)
{
content.Dispose();
content = null;
}
}
}
#region Fields
///
/// Retreive the Gui Sheet that is the root node of the tree of all CeGui#
/// windows on the display
///
/// the Gui Sheet
/// RK: BTW use a property named Sheet instead.
/// DT: Except the Gui Sheet ISN'T owned by the ScreenManager.
public static CeGui.GuiSheet RootGuiSheet
{
get { return (CeGui.GuiSheet)CeGui.WindowManager.Instance.GetWindow("Root"); }
}
///
/// The CeGui gui builder used to create widgets (that we later attach to screens/dialogs)
///
public CeGui.GuiBuilder GuiBuilder { get { return guiBuilder; } }
///
/// The CeGui gui builder used to create widgets (that we later attach to screens/dialogs)
///
private CeGui.GuiBuilder guiBuilder = new CeGui.WidgetSets.Taharez.TLGuiBuilder();
///
/// The dialogs that are waiting to be shown to the user
///
private List