#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 queuedDialogs = new List(); /// /// The dialogs that are being shown to the user /// private Stack showingDialogs = new Stack(); /// /// Used for tracking FPS stats /// int frameCount; /// /// Used for tracking FPS stats /// DateTime lastOutput = DateTime.Now; /// The content manager private ContentManager content; /// The screen to replace current screen with on next update private Screen nextScreen; /// The screen to put on over the top of current screen on next update private Screen pushScreen; /// Flag to say dispose of screen on top of screenStack, and replace with next lower down private bool popScreen; /// Stack of screens (topmost is one currently being shown) private Stack screenStack = new Stack(); #endregion Fields /// /// Assorted calcs to put the FPS rate at top of screen /// private void fpsCalcs() { ++frameCount; DateTime rightNow = DateTime.Now; double seconds = (rightNow - lastOutput).TotalMilliseconds / 1000.0; if (1.0 < seconds) { Xenocide.Instance.Window.Title = Util.StringFormat("Xenocide.exe fps = {0}", (frameCount / seconds)); frameCount = 0; lastOutput = rightNow; } } } }