#region CPL License /* Nuclex Framework Copyright (C) 2002-2010 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Graphics; using Nuclex.Input; using GameEventHandler = System.EventHandler; namespace Nuclex.UserInterface { // TODO: Ownership issue with the GUI visualizer // If an instance creates its own GUI visualizer (because the user didn't assign // a custom one), it belongs to the instance and should be disposed. If the // use does assign a custom visualizer, it shouldn't be disposed. // But what if the user stores the current visualizer, then assigns a different // one and assigns our's back? /// Manages the state of the user interfaces and renders it public class GuiManager : IGameComponent, IUpdateable, IDrawable, IDisposable, IGuiService { /// Fired when the DrawOrder property changes public event GameEventHandler DrawOrderChanged; /// Fired when the Visible property changes public event GameEventHandler VisibleChanged; /// Fired when the UpdateOrder property changes public event GameEventHandler UpdateOrderChanged; /// Fired when the enabled property changes, which is never event GameEventHandler IUpdateable.EnabledChanged { add { } remove { } } /// /// Initializes a new GUI manager using the XNA service container /// /// /// Game service container the GuiManager will register itself in and /// to take the services it consumes from. /// public GuiManager(GameServiceContainer gameServices) { this.gameServices = gameServices; gameServices.AddService(typeof(IGuiService), this); // Do not look for the consumed services yet. XNA uses a two-stage // initialization where in the first stage all managers register themselves // before seeking out the services they use in the second stage, marked // by a call to the Initialize() method. } /// /// Initializes a new GUI manager without using the XNA service container /// /// /// Graphics device service the GUI will be rendered with /// /// /// Input service used to read data from the input devices /// /// /// This constructor is provided for users of dependency injection frameworks. /// public GuiManager( IGraphicsDeviceService graphicsDeviceService, IInputService inputService ) { this.graphicsDeviceService = graphicsDeviceService; this.inputService = inputService; } /// Initializes a new GUI manager using explicit services /// /// Game service container the GuiManager will register itself in /// /// /// Graphics device service the GUI will be rendered with /// /// /// Input service used to read data from the input devices /// /// /// This constructor is provided for users of dependency injection frameworks /// or if you just want to be more explicit in stating which manager consumes /// what services. /// public GuiManager( GameServiceContainer gameServices, IGraphicsDeviceService graphicsDeviceService, IInputService inputService ) : this(gameServices) { this.graphicsDeviceService = graphicsDeviceService; this.inputService = inputService; } /// Immediately releases all resources used the GUI manager public void Dispose() { // Unregister the service if we have registered it before if (this.gameServices != null) { object registeredService = this.gameServices.GetService(typeof(IGuiService)); if (ReferenceEquals(registeredService, this)) { this.gameServices.RemoveService(typeof(IGuiService)); } } // Dispose the input capturer, if necessary if (this.inputCapturer != null) { IDisposable disposableInputCapturer = this.inputCapturer as IDisposable; if (disposableInputCapturer != null) { disposableInputCapturer.Dispose(); } this.updateableInputCapturer = null; this.inputCapturer = null; } // Dispose the GUI visualizer, if necessary if (this.guiVisualizer != null) { IDisposable disposableguiVisualizer = this.guiVisualizer as IDisposable; if (disposableguiVisualizer != null) { disposableguiVisualizer.Dispose(); } this.updateableGuiVisualizer = null; this.guiVisualizer = null; } } /// Handles second-stage initialization of the GUI manager public void Initialize() { // Set up a default input capturer if none was assigned by the user. // We only require an IInputService if the user doesn't use a custom input // capturer (which could be based on any other input library) if (this.inputCapturer == null) { if (this.inputService == null) { this.inputService = getInputService(this.gameServices); } this.inputCapturer = new Input.DefaultInputCapturer(this.inputService); // If a screen was assigned to the GUI before the input capturer was // created, then the input capturer hasn't been given the screen as its // input sink yet. if (this.screen != null) { this.inputCapturer.InputReceiver = this.screen; } } // Set up a default GUI visualizer if none was assigned by the user. // We only require an IGraphicsDeviceService if the user doesn't use a // custom visualizer (which could be using any kind of rendering) if (this.guiVisualizer == null) { if (this.graphicsDeviceService == null) { this.graphicsDeviceService = getGraphicsDeviceService(this.gameServices); } // Use a private service container. We know exactly what will be loaded from // the content manager our default GUI visualizer creates and if the user is // being funny, the graphics device service passed to the constructor might // be different from the one registered in the game service container. var services = new GameServiceContainer(); services.AddService(typeof(IGraphicsDeviceService), this.graphicsDeviceService); Visualizer = Visuals.Flat.FlatGuiVisualizer.FromResource( services, Resources.SuaveSkinResources.ResourceManager, "SuaveSkin" ); } } /// GUI that is being rendered /// /// The GUI manager renders one GUI full-screen onto the primary render target /// (the backbuffer). This property holds the GUI that is being managed by /// the GUI manager component. You can replace it at any time, for example, /// if the player opens or closes your ingame menu. /// public Screen Screen { get { return this.screen; } set { this.screen = value; // Someone could assign the screen before the component is initialized. // If that happens, do nothing here, the Initialize() method will take care // of assigning the screen to the input capturer once it is called. if (this.inputCapturer != null) { this.inputCapturer.InputReceiver = this.screen; } } } /// Input capturer that collects data from the input devices /// /// The GuiManager will dispose its input capturer together with itself. If you /// want to keep the input capturer, unset it before disposing the GuiManager. /// If you want to replace the GuiManager's input capturer after it has constructed /// the default one, you should dispose the GuiManager's default input capturer /// after assigning your own. /// public Input.IInputCapturer InputCapturer { get { return this.inputCapturer; } set { if (!ReferenceEquals(value, this.inputCapturer)) { if (this.inputCapturer != null) { this.inputCapturer.InputReceiver = null; } this.inputCapturer = value; this.updateableInputCapturer = value as IUpdateable; if (this.inputCapturer != null) { this.inputCapturer.InputReceiver = this.screen; } } } } /// Visualizer that draws the GUI onto the screen /// /// The GuiManager will dispose its visualizer together with itself. If you want /// to keep the visualizer, unset it before disposing the GuiManager. If you want /// to replace the GuiManager's visualizer after it has constructed the default /// one, you should dispose the GuiManager's default visualizer after assigning /// your own. /// public Visuals.IGuiVisualizer Visualizer { get { return this.guiVisualizer; } set { this.guiVisualizer = value; this.updateableGuiVisualizer = value as IUpdateable; } } /// Called when the component needs to update its state. /// Provides a snapshot of the Game's timing values public void Update(GameTime gameTime) { if (this.updateableInputCapturer != null) { this.updateableInputCapturer.Update(gameTime); } if (this.updateableGuiVisualizer != null) { this.updateableGuiVisualizer.Update(gameTime); } } /// Called when the drawable component needs to draw itself. /// Provides a snapshot of the game's timing values public void Draw(GameTime gameTime) { if (this.guiVisualizer != null) { if (this.screen != null) { this.guiVisualizer.Draw(this.screen); } } } /// /// Indicates when the game component should be updated relative to other game /// components. Lower values are updated first. /// public int UpdateOrder { get { return this.updateOrder; } set { if (value != this.updateOrder) { this.updateOrder = value; OnUpdateOrderChanged(); } } } /// /// The order in which to draw this object relative to other objects. Objects /// with a lower value are drawn first. /// public int DrawOrder { get { return this.drawOrder; } set { if (value != this.drawOrder) { this.drawOrder = value; OnDrawOrderChanged(); } } } /// Whether the GUI should be drawn during Game.Draw() public bool Visible { get { return this.visible; } set { if (value != this.visible) { this.visible = value; OnVisibleChanged(); } } } /// Fires the UpdateOrderChanged event protected void OnUpdateOrderChanged() { if (UpdateOrderChanged != null) { UpdateOrderChanged(this, EventArgs.Empty); } } /// Fires the DrawOrderChanged event protected void OnDrawOrderChanged() { if (DrawOrderChanged != null) { DrawOrderChanged(this, EventArgs.Empty); } } /// Fires the VisibleChanged event protected void OnVisibleChanged() { if (VisibleChanged != null) { VisibleChanged(this, EventArgs.Empty); } } /// Whether the component should be updated during Game.Update() bool IUpdateable.Enabled { get { return true; } } /// Retrieves the input service from a service provider /// /// Service provider the input service is retrieved from /// /// The retrieved input service private static IInputService getInputService(IServiceProvider serviceProvider) { var inputService = (IInputService)serviceProvider.GetService( typeof(IInputService) ); if (inputService == null) { throw new InvalidOperationException( "Using the GUI with the default input capturer requires the IInputService. " + "Please either add the IInputService to Game.Services by using the " + "Nuclex.Input.InputManager in your game or provide a custom IInputCapturer " + "implementation for the GUI and assign it before GuiManager.Initialize() " + "is called." ); } return inputService; } /// Retrieves the graphics device service from a service provider /// /// Service provider the graphics device service is retrieved from /// /// The retrieved graphics device service private static IGraphicsDeviceService getGraphicsDeviceService( IServiceProvider serviceProvider ) { var graphicsDeviceService = (IGraphicsDeviceService)serviceProvider.GetService( typeof(IGraphicsDeviceService) ); if (graphicsDeviceService == null) { throw new InvalidOperationException( "Using the GUI with the default visualizer requires the IGraphicsDeviceService. " + "Please either add an IGraphicsDeviceService to Game.Services by using " + "XNA's GraphicsDeviceManager in your game or provide a custom " + "IGuiVisualizer implementation for the GUI and assign it before " + "GuiManager.Initialize() is called." ); } return graphicsDeviceService; } /// Game service container the GUI has registered itself in private GameServiceContainer gameServices; /// Graphics device servide the GUI uses private IGraphicsDeviceService graphicsDeviceService; /// Input service the GUI uses private IInputService inputService; /// Update order rank relative to other game components private int updateOrder; /// Draw order rank relative to other game components private int drawOrder; /// Whether the GUI should be drawn by Game.Draw() private bool visible = true; /// Captures user input for the XNA game private Input.IInputCapturer inputCapturer; /// /// The IInputCapturer under its IUpdateable interface, if implemented /// private IUpdateable updateableInputCapturer; /// Draws the GUI private Visuals.IGuiVisualizer guiVisualizer; /// /// The IGuiVisualizer under its IUpdateable interface, if implemented /// private IUpdateable updateableGuiVisualizer; /// The GUI screen representing the desktop private Screen screen; } } // namespace Nuclex.UserInterface