#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 System.Diagnostics; using System.IO; using System.Reflection; using System.Resources; using Microsoft.Xna.Framework.Content; using Nuclex.Support; using Nuclex.Support.Plugins; namespace Nuclex.UserInterface.Visuals.Flat { // TODO: This implements the Drawable class but doesn't override Draw() // Having two overloads of the Draw() method, one doing nothing, is confusing // and should be avoided. Find a better solution. Perhaps we can rely completely // on the virtualized graphics device here. /// Draws traditional flat GUIs using 2D bitmaps public class FlatGuiVisualizer : IGuiVisualizer, IDisposable { #region struct ControlWithBounds /// Container for a control and its absolute boundaries struct ControlWithBounds { /// Initializes a new control and absolute boundary container /// Control being store in the container /// Absolute boundaries the control lives in public ControlWithBounds(Controls.Control control, RectangleF bounds) { this.Control = control; this.Bounds = bounds; } /// /// Builds an absolute boundary container from the provided control /// /// Control from which a container will be created /// /// Absolute boundaries of the control's parent /// /// A new container with the control public static ControlWithBounds FromControl( Controls.Control control, RectangleF containerBounds ) { containerBounds.X += control.Bounds.Location.X.Fraction * containerBounds.Width; containerBounds.X += control.Bounds.Location.X.Offset; containerBounds.Y += control.Bounds.Location.Y.Fraction * containerBounds.Height; containerBounds.Y += control.Bounds.Location.Y.Offset; containerBounds.Width = control.Bounds.Size.X.ToOffset(containerBounds.Width); containerBounds.Height = control.Bounds.Size.Y.ToOffset(containerBounds.Height); return new ControlWithBounds(control, containerBounds); } /// /// Builds a control and absolute boundary container from a screen /// /// /// Screen whose desktop control and absolute boundaries are used to /// construct the container /// /// A new container with the screen's desktop control public static ControlWithBounds FromScreen(Screen screen) { return new ControlWithBounds( screen.Desktop, screen.Desktop.Bounds.ToOffset(screen.Width, screen.Height) ); } /// Control stored in the container public Controls.Control Control; /// Absolute boundaries of the stored control public RectangleF Bounds; } #endregion // struct ControlWithBounds #region interface IControlRendererAdapter /// Interface for a generic (typeless) control renderer internal interface IControlRendererAdapter { /// /// Renders the specified control using the provided graphics interface /// /// Control that will be rendered /// /// Graphics interface that will be used to render the control /// void Render(Controls.Control controlToRender, IFlatGuiGraphics graphics); /// The type of the control renderer being adapted Type AdaptedType { get; } } #endregion // interface IControlRendererAdapter #region class ControlRendererAdapter<> /// /// Adapter that automatically casts a control down to the renderer's supported /// control type /// /// /// Type of control the control renderer casts down to /// /// /// This is simply an optimization to avoid invoking the control renderer /// by reflection (using the Invoke() method) which would require us to construct /// an object[] array on the heap to pass its arguments. /// private class ControlRendererAdapter : IControlRendererAdapter where ControlType : Controls.Control { /// Initializes a new control renderer adapter /// Control renderer the adapter is used for public ControlRendererAdapter(IFlatControlRenderer controlRenderer) { this.controlRenderer = controlRenderer; } /// /// Renders the specified control using the provided graphics interface /// /// Control that will be rendered /// /// Graphics interface that will be used to render the control /// public void Render(Controls.Control controlToRender, IFlatGuiGraphics graphics) { this.controlRenderer.Render((ControlType)controlToRender, graphics); } /// The type of the control renderer being adapted public Type AdaptedType { get { return this.controlRenderer.GetType(); } } /// Control renderer this adapter is performing the downcast for private IFlatControlRenderer controlRenderer; } #endregion // class ControlRendererAdapter<> #region class ControlRendererEmployer /// /// Employs concrete types implementing IFlatGuiControlRenderer<> /// /// /// This employer actually looks for concrete implementations using a variant /// of the IFlatGuiControlRenderer<> interface, regardless of the /// type it has been realized for. /// internal class ControlRendererEmployer : Employer { /// Initializes a new control renderer employer public ControlRendererEmployer() { this.renderers = new Dictionary(); } /// Determines whether the type suites the employer's requirements /// Type that is checked for employability /// True if the type can be employed public override bool CanEmploy(Type type) { // If the type doesn't implement the IFlatcontrolRenderer interface, there's // no chance that it will implement one of the generic control drawers if(!typeof(IFlatControlRenderer).IsAssignableFrom(type)) { return false; } // We also need a default constructor in order to be able to create an // instance of this renderer if(!type.HasDefaultConstructor()) { return false; } // Look for the IFlatControlRenderer<> interface in all interfaces implemented // by this type Type[] implementedInterfaces = type.GetInterfaces(); for(int index = 0; index < implementedInterfaces.Length; ++index) { // Only perform further checks if this interface is actually generic if(implementedInterfaces[index].IsGenericType) { Type genericType = implementedInterfaces[index].GetGenericTypeDefinition(); if(genericType == typeof(IFlatControlRenderer<>)) { return true; } } } // The interface we were looking for was not found, therefore, this is // not an employable type return false; } /// Employs the specified plugin type /// Type to be employed public override void Employ(Type type) { // Obtain all the interfaces of the employed type and search them one by one. // We need to take this route because there's no method that would allow us to // look up the generic interface in its unspecialized form with a simple call. Type[] implementedInterfaces = type.GetInterfaces(); for(int index = 0; index < implementedInterfaces.Length; ++index) { // Only perform further checks if this interface is actually a generic one if(implementedInterfaces[index].IsGenericType) { // Get the (unspecialized) generic form of this interface and see if it's // the interface we're looking for Type genericType = implementedInterfaces[index].GetGenericTypeDefinition(); if(genericType == typeof(IFlatControlRenderer<>)) { // Find out which control type the renderer is specialized for Type[] controlType = implementedInterfaces[index].GetGenericArguments(); // Do we already have a renderer for this control type? if(this.renderers.ContainsKey(controlType[0])) { #if !(XBOX360 || WINDOWS_PHONE) // We found another renderer for a control type that already has // a renderer. At least print out a warning to the debug log about this. string message = string.Format( "Warning: Control type '{0}' already using renderer '{1}'.\n" + " Second renderer '{2}' will be ignored!", controlType[0].FullName.ToString(), this.renderers[controlType[0]].AdaptedType.FullName.ToString(), type.FullName.ToString() ); System.Diagnostics.Trace.WriteLine(message); #endif } else { // No, this is the first renderer we found for this control type // Type of the downcast adapter we need to bring to life Type adapterType = typeof(ControlRendererAdapter<>).MakeGenericType( controlType[0] ); // Look up the constructor of the downcast adapter ConstructorInfo adapterConstructor = adapterType.GetConstructor( new Type[] { implementedInterfaces[index] } ); // Now use that constructor to create an instance object adapterInstance = adapterConstructor.Invoke( new object[] { Activator.CreateInstance(type) } ); // Employ the new adapter and thereby the control renderer it adapts this.renderers.Add(controlType[0], (IControlRendererAdapter)adapterInstance); } } } } } /// Renderers that were employed to the plugin host public Dictionary Renderers { get { return this.renderers; } } /// Employed renderers private Dictionary renderers; } #endregion // class ControlRendererEmployer /// Initializes a new gui visualizer from a skin stored in a file /// /// Game service provider containing the graphics device service /// /// /// Path to the skin description this GUI visualizer will load /// public static FlatGuiVisualizer FromFile( IServiceProvider serviceProvider, string skinPath ) { using( FileStream skinStream = new FileStream( skinPath, FileMode.Open, FileAccess.Read, FileShare.Read ) ) { ContentManager contentManager = new ContentManager( serviceProvider, Path.GetDirectoryName(skinPath) ); try { return new FlatGuiVisualizer(contentManager, skinStream); } catch(Exception) { contentManager.Dispose(); throw; } } } /// Initializes a new gui visualizer from a skin stored as a resource /// /// Game service provider containing the graphics device service /// /// /// Resource manager containing the resources used in the skin /// /// /// Name of the resource containing the skin description /// public static FlatGuiVisualizer FromResource( IServiceProvider serviceProvider, ResourceManager resourceManager, string skinResource ) { byte[] resourceData = (byte[])resourceManager.GetObject(skinResource); if(resourceData == null) { throw new ArgumentException( "Resource '" + skinResource + "' not found", "skinResource" ); } // This sucks! I cannot use ResourceManager.GetStream() on a resource that's // stored as a byte array! using( MemoryStream skinStream = new MemoryStream(resourceData, false) ) { ResourceContentManager contentManager = new ResourceContentManager( serviceProvider, resourceManager ); try { return new FlatGuiVisualizer(contentManager, skinStream); } catch(Exception) { contentManager.Dispose(); throw; } } } /// Initializes a new gui painter for traditional GUIs /// /// Content manager that will be used to load the skin resources /// /// /// Stream from which the GUI Visualizer will read the skin description /// protected FlatGuiVisualizer(ContentManager contentManager, Stream skinStream) { this.employer = new ControlRendererEmployer(); this.pluginHost = new PluginHost(this.employer); // Employ our own assembly in order to obtain the default GUI renderers this.pluginHost.Repository.AddAssembly(Self); this.flatGuiGraphics = new FlatGuiGraphics(contentManager, skinStream); this.controlStack = new Stack(); } /// Immediately releases all resources owned by the instance public void Dispose() { if(this.flatGuiGraphics != null) { this.flatGuiGraphics.Dispose(); this.flatGuiGraphics = null; } } /// Draws an entire GUI hierarchy /// Screen containing the GUI that will be drawn public void Draw(Screen screen) { this.flatGuiGraphics.BeginDrawing(); try { this.controlStack.Push(ControlWithBounds.FromScreen(screen)); while(controlStack.Count > 0) { ControlWithBounds controlWithBounds = this.controlStack.Pop(); Controls.Control currentControl = controlWithBounds.Control; RectangleF currentBounds = controlWithBounds.Bounds; // Add the controls in normal order, so the first control in the collection will // be taken off the stack last, ensuring it's rendered on top of all others. for(int index = 0; index < currentControl.Children.Count; ++index) { this.controlStack.Push( ControlWithBounds.FromControl(currentControl.Children[index], currentBounds) ); } renderControl(currentControl); } } finally { this.flatGuiGraphics.EndDrawing(); } } /// /// Plugin repository from which renderers for GUI controls are taken /// public PluginRepository RendererRepository { get { return this.pluginHost.Repository; } } /// Renders a single control /// Control that will be rendered private void renderControl(Controls.Control controlToRender) { IControlRendererAdapter renderer = null; Type controlType = controlToRender.GetType(); // If this is an actual instance of the 'Control' class, don't render it. // Such instances can be used to construct invisible containers, and are most // prominently embodied in the 'desktop' control that hosts the whole GUI. if( (controlType == typeof(Controls.Control)) || (controlType == typeof(Controls.DesktopControl)) ) { return; } // Find a renderer for this control. If no renderer for the control itself can // be found, look for a renderer then can render its base class. This allows // controls to inherit from existing controls, remaining renderable (but also // gaining the ability to accept a specialized renderer for the new derived // control class!). Normally, this loop will finish without any repetitions. while(controlType != typeof(object)) { bool found = this.employer.Renderers.TryGetValue(controlType, out renderer); if(found) { break; } // Next, try the base class of this type controlType = controlType.BaseType; } // If we found a renderer, use it to render the control if(renderer != null) { renderer.Render(controlToRender, this.flatGuiGraphics); } else { // No renderer found, output a warning #if WINDOWS Trace.WriteLine( string.Format( "Warning: No renderer found for control '{0}' or any of its base classes.\n" + " Control will not be rendered.", controlToRender.GetType().FullName.ToString() ) ); #endif } } /// Returns the assembly containing the GUI visualizer private static Assembly Self { get { return typeof(FlatGuiVisualizer).Assembly; } } /// Holds the assemblies we have employed for our cause private PluginHost pluginHost; /// Carries the employed control renderers private ControlRendererEmployer employer; /// Used to draw the individual building elements of the GUI private FlatGuiGraphics flatGuiGraphics; /// Helps draw the GUI controls in the hierarchically correct order /// /// This is a field and not a local variable because the stack allocates /// heap memory and we don't want that to happen in a frame-by-frame basis on /// the compact framework. By reusing the same stack over and over, the amount /// of heap allocations required will amortize itself. /// private Stack controlStack; } } // namespace Nuclex.UserInterface.Visuals.Flat