#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.IO; using System.Reflection; using System.Resources; using System.Text; using System.Xml; using System.Xml.Schema; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; namespace Nuclex.UserInterface.Visuals.Flat { /// Graphics interface for the traditional flat GUI visualizer /// /// This class is analog to System.Drawing.Graphics, but contains specialized /// methods that allow the FlatControlRenderers to draw controls from /// high-level elements which are controlled by loadable XML themes. /// public partial class FlatGuiGraphics : IFlatGuiGraphics, IDisposable { /// Width of the caret used for text input const float CaretWidth = 2.0f; #region struct Frame /// Frame that can be drawn by the GUI painter private class Frame { /// Modes in which text can be horizontally aligned public enum HorizontalTextAlignment { /// The text's base offset is placed at the left of the frame /// /// The base offset is normally identical to the text's leftmost pixel. /// However, a glyph may have some eccentrics like an arc that extends to /// the left over the letter's actual starting position. /// Left, /// /// The text's ending offset is placed at the right of the frame /// /// /// The ending offset is normally identical to the text's rightmost pixel. /// However, a glyph may have some eccentrics like an arc that extends to /// the right over the last letter's actual ending position. /// Right, /// The text is centered horizontally in the frame Center } /// Modes in which text can be vertically aligned public enum VerticalTextAlignment { /// The text's baseline is placed at the top of the frame Top, /// The text's baseline is placed at the bottom of the frame Bottom, /// The text's baseline is centered vertically in the frame Center } /// Defines a picture region drawn into a frame public struct Region { /// Identification string for the region /// /// Used to associate regions with specific behavior /// public string Id; /// Texture the picture region is taken from public Texture2D Texture; /// Area within the texture containing the picture region public Rectangle SourceRegion; /// Location in the frame where the picture region will be drawn public UniRectangle DestinationRegion; } /// Describes where within the frame text should be drawn public struct Text { /// Font to use for drawing the text public SpriteFont Font; /// Offset of the text relative to its specified placement public Point Offset; /// Horizontal placement of the text within the frame public HorizontalTextAlignment HorizontalPlacement; /// Vertical placement of the text within the frame public VerticalTextAlignment VerticalPlacement; /// Color the text will have public Color Color; } /// Initializes a new frame /// Regions needed to be drawn to render the frame /// Location in the frame where text can be drawn public Frame(Region[] regions, Text[] texts) { this.Regions = regions; this.Texts = texts; } /// Regions that need to be drawn to render the frame public Region[] Regions; /// Locations where text can be drawn into the frame public Text[] Texts; } #endregion // struct Frame #region class ScissorKeeper /// Manages the scissor rectangle for the GUI graphics interface private class ScissorKeeper : IDisposable { /// Initializes a new scissor manager /// /// GUI graphics interface the scissor rectangle will be managed for /// public ScissorKeeper(FlatGuiGraphics flatGuiGraphics) { this.flatGuiGraphics = flatGuiGraphics; } /// Assigns the scissor rectangle to the graphics device /// Scissor rectangle that will be assigned public void Assign(ref Rectangle clipRegion) { this.flatGuiGraphics.endSpriteBatch(); try { GraphicsDevice graphics = this.flatGuiGraphics.spriteBatch.GraphicsDevice; this.oldScissorRectangle = graphics.ScissorRectangle; graphics.ScissorRectangle = clipRegion; } finally { this.flatGuiGraphics.beginSpriteBatch(); } } /// Releases the currently assigned scissor rectangle again public void Dispose() { this.flatGuiGraphics.endSpriteBatch(); try { GraphicsDevice graphics = this.flatGuiGraphics.spriteBatch.GraphicsDevice; graphics.ScissorRectangle = this.oldScissorRectangle; } finally { this.flatGuiGraphics.beginSpriteBatch(); } } /// /// GUI graphics interface for which the scissor rectangle is managed /// private FlatGuiGraphics flatGuiGraphics; /// /// Scissor rectangle that was previously assigned to the graphics device /// private Rectangle oldScissorRectangle; } #endregion // class ScissorKeeper /// Initializes a new gui painter /// /// Content manager containing the resources for the GUI. The instance takes /// ownership of the content manager and will dispose it. /// /// /// Stream from which the skin description will be read /// public FlatGuiGraphics(ContentManager contentManager, Stream skinStream) { IGraphicsDeviceService graphicsDeviceService = (IGraphicsDeviceService)contentManager.ServiceProvider.GetService( typeof(IGraphicsDeviceService) ); this.spriteBatch = new SpriteBatch(graphicsDeviceService.GraphicsDevice); this.contentManager = contentManager; this.openingLocator = new OpeningLocator(); this.stringBuilder = new StringBuilder(64); this.scissorManager = new ScissorKeeper(this); this.rasterizerState = new RasterizerState() { ScissorTestEnable = true }; this.fonts = new Dictionary(); this.bitmaps = new Dictionary(); this.frames = new Dictionary(); loadSkin(skinStream); } /// Immediately releases all resources owned by the instance public void Dispose() { if(this.contentManager != null) { this.contentManager.Dispose(); this.contentManager = null; } if(this.spriteBatch != null) { this.spriteBatch.Dispose(); this.spriteBatch = null; } } /// /// Positions a string within a frame according to the positioning instructions /// stored in the provided text anchor. /// /// Text anchor the string will be positioned for /// Boundaries of the control the string is rendered in /// String that will be positioned /// The position of the string within the control private Vector2 positionText(ref Frame.Text anchor, RectangleF bounds, string text) { Vector2 textSize = anchor.Font.MeasureString(text); float x, y; switch(anchor.HorizontalPlacement) { case Frame.HorizontalTextAlignment.Left: { x = bounds.Left; break; } case Frame.HorizontalTextAlignment.Right: { x = bounds.Right - textSize.X; break; } case Frame.HorizontalTextAlignment.Center: default: { x = (bounds.Width - textSize.X) / 2.0f + bounds.Left; break; } } switch(anchor.VerticalPlacement) { case Frame.VerticalTextAlignment.Top: { y = bounds.Top; break; } case Frame.VerticalTextAlignment.Bottom: { y = bounds.Bottom - anchor.Font.LineSpacing; break; } case Frame.VerticalTextAlignment.Center: default: { y = (bounds.Height - anchor.Font.LineSpacing) / 2.0f + bounds.Top; break; } } return new Vector2(floor(x + anchor.Offset.X), floor(y + anchor.Offset.Y)); } /// /// Calculates the absolute pixel position of a rectangle in unified coordinates /// /// Bounds of the drawing area in pixels /// Destination rectangle in unified coordinates /// /// The destination rectangle converted to absolute pixel coordinates /// private static Rectangle calculateDestinationRectangle( ref RectangleF bounds, ref UniRectangle destination ) { int x = (int)(bounds.X + destination.Location.X.Offset); x += (int)(bounds.Width * destination.Location.X.Fraction); int y = (int)(bounds.Y + destination.Location.Y.Offset); y += (int)(bounds.Height * destination.Location.Y.Fraction); int width = (int)(destination.Size.X.Offset); width += (int)(bounds.Width * destination.Size.X.Fraction); int height = (int)(destination.Size.Y.Offset); height += (int)(bounds.Height * destination.Size.Y.Fraction); return new Rectangle(x, y, width, height); } /// Looks up the frame with the specified name /// Frame that will be looked up /// The frame with the specified name private Frame lookupFrame(string frameName) { // Make sure the renderer specified a valid frame name. If someone modifies // the skin or uses a skin which does not support all required controls, // this will provide the user with a clear error message. Frame frame; if(!this.frames.TryGetValue(frameName, out frame)) { throw new ArgumentException( "Unknown frame type: '" + frameName + "'", "frameName" ); } return frame; } /// Removes the fractional part from the floating point value /// Value whose fractional part will be removed /// The floating point value without its fractional part private static float floor(float value) { return (float)Math.Floor((double)value); } /// String builder used for various purposes in this class private StringBuilder stringBuilder; /// Locates openings between letters in strings private OpeningLocator openingLocator; /// Manages the scissor rectangle and its assignment time private ScissorKeeper scissorManager; /// Batches GUI elements for faster drawing private SpriteBatch spriteBatch; /// Manages the content used to draw the GUI private ContentManager contentManager; /// Font styles known to the GUI private Dictionary fonts; /// Bitmaps containing resources for the GUI private Dictionary bitmaps; /// Types of frames the painter can draw private Dictionary frames; /// Rasterizer state used for drawing the GUI private RasterizerState rasterizerState; } } // namespace Nuclex.UserInterface.Visuals.Flat