#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 #if !USE_XMLDOCUMENT using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Xml; using System.Xml.Linq; using System.Xml.Schema; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; using Nuclex.Support; namespace Nuclex.UserInterface.Visuals.Flat { partial class FlatGuiGraphics { #region class RegionListBuilder /// Builds a region list from the regions in an frame XML node private class RegionListBuilder { /// Initializes a new frame region list builder private RegionListBuilder() { } /// /// Builds a region list from the regions specified in the provided frame XML node /// /// /// XML node for the frame whose regions wille be processed /// /// /// Bitmap lookup table used to associate a region's bitmap id to the real bitmap /// /// /// A list of the regions that have been extracted from the frame XML node /// public static Frame.Region[] Build( XElement frameElement, IDictionary bitmaps ) { RegionListBuilder builder = new RegionListBuilder(); builder.retrieveBorderSizes(frameElement); return builder.createAndPlaceRegions(frameElement, bitmaps); } /// Retrieves the sizes of the border regions in a frame /// /// XML node for the frame containing the region /// private void retrieveBorderSizes(XElement frameElement) { foreach (XElement element in frameElement.Descendants("region")) { // Left and right border width determination string hplacement = element.Attribute("hplacement").Value; string w = element.Attribute("w").Value; if (hplacement == "left") { this.leftBorderWidth = Math.Max(this.leftBorderWidth, int.Parse(w)); } else if (hplacement == "right") { this.rightBorderWidth = Math.Max(this.rightBorderWidth, int.Parse(w)); } // Top and bottom border width determination string vplacement = element.Attribute("vplacement").Value; string h = element.Attribute("h").Value; if (vplacement == "top") { this.topBorderWidth = Math.Max(this.topBorderWidth, int.Parse(h)); } else if (vplacement == "bottom") { this.bottomBorderWidth = Math.Max(this.bottomBorderWidth, int.Parse(h)); } } } /// /// Creates and places the regions needed to be drawn to render the frame /// /// /// XML node for the frame containing the region /// /// /// Bitmap lookup table to associate a region's bitmap id to the real bitmap /// /// The regions created for the frame private Frame.Region[] createAndPlaceRegions( XElement frameElement, IDictionary bitmaps ) { var regions = new List(); // Fill all regions making up the current frame foreach (XElement element in frameElement.Descendants("region")) { XAttribute idAttribute = element.Attribute("id"); string id = (idAttribute == null) ? null : idAttribute.Value; string source = element.Attribute("source").Value; string hplacement = element.Attribute("hplacement").Value; string vplacement = element.Attribute("vplacement").Value; string x = element.Attribute("x").Value; string y = element.Attribute("y").Value; string w = element.Attribute("w").Value; string h = element.Attribute("h").Value; // Assign the trivial attributes var region = new Frame.Region() { Id = id, Texture = bitmaps[source] }; region.SourceRegion.X = int.Parse(x); region.SourceRegion.Y = int.Parse(y); region.SourceRegion.Width = int.Parse(w); region.SourceRegion.Height = int.Parse(h); // Process each region's placement and set up the unified coordinates calculateRegionPlacement( getHorizontalPlacementIndex(hplacement), int.Parse(w), this.leftBorderWidth, this.rightBorderWidth, ref region.DestinationRegion.Location.X, ref region.DestinationRegion.Size.X ); calculateRegionPlacement( getVerticalPlacementIndex(vplacement), int.Parse(h), this.topBorderWidth, this.bottomBorderWidth, ref region.DestinationRegion.Location.Y, ref region.DestinationRegion.Size.Y ); regions.Add(region); } return regions.ToArray(); } /// /// Calculates the unified coordinates a region needs to be placed at /// /// /// Placement index indicating where in a frame the region will be located /// /// Width of the region in pixels /// /// Width of the border on the lower end of the coordinate range /// /// /// Width of the border on the higher end of the coordinate range /// /// /// Receives the target location of the region in unified coordinates /// /// /// Receives the size of the region in unified coordinates /// private void calculateRegionPlacement( int placementIndex, int width, int lowBorderWidth, int highBorderWidth, ref UniScalar location, ref UniScalar size ) { switch (placementIndex) { case (-1): { // left or top int gapWidth = lowBorderWidth - width; location.Fraction = 0.0f; location.Offset = (float)gapWidth; size.Fraction = 0.0f; size.Offset = (float)width; break; } case (+1): { // right or bottom location.Fraction = 1.0f; location.Offset = -(float)highBorderWidth; size.Fraction = 0.0f; size.Offset = (float)width; break; } case (0): { // stretch location.Fraction = 0.0f; location.Offset = (float)lowBorderWidth; size.Fraction = 1.0f; size.Offset = -(float)(highBorderWidth + lowBorderWidth); break; } } } /// Converts a horizontal placement string into a placement index /// String containing the horizontal placement /// A placement index that is equivalent to the provided string private int getHorizontalPlacementIndex(string placement) { switch (placement) { case "left": { return -1; } case "right": { return +1; } case "stretch": default: { return 0; } } } /// Converts a vertical placement string into a placement index /// String containing the vertical placement /// A placement index that is equivalent to the provided string private int getVerticalPlacementIndex(string placement) { switch (placement) { case "top": { return -1; } case "bottom": { return +1; } case "stretch": default: { return 0; } } } /// Width of the frame's left border regions private int leftBorderWidth; /// Width of the frame's top border regions private int topBorderWidth; /// Width of the frame's right border regions private int rightBorderWidth; /// Width of the frame's bottom border regions private int bottomBorderWidth; } #endregion // class RegionListBuilder #region class TextListBuilder /// Builds a text list from the regions in an frame XML node private class TextListBuilder { /// /// Builds a text list from the text placements specified in the provided node /// /// /// XML node for the frame whose text placements wille be processed /// /// /// Font lookup table used to associate a text's font id to the real font /// /// /// A list of the texts that have been extracted from the frame XML node /// public static Frame.Text[] Build( XElement frameElement, IDictionary fonts ) { var texts = new List(); foreach(XElement element in frameElement.Descendants("text")) { string font = element.Attribute("font").Value; string horizontalPlacement = element.Attribute("hplacement").Value; string verticalPlacement = element.Attribute("vplacement").Value; XAttribute xOffsetAttribute = element.Attribute("xoffset"); int xOffset = (xOffsetAttribute == null) ? 0 : int.Parse(xOffsetAttribute.Value); XAttribute yOffsetAttribute = element.Attribute("yoffset"); int yOffset = (yOffsetAttribute == null) ? 0 : int.Parse(yOffsetAttribute.Value); XAttribute colorAttribute = element.Attribute("color"); Color color; if (colorAttribute == null) { color = Color.White; } else { color = colorFromString(colorAttribute.Value); } var text = new Frame.Text() { Font = fonts[font], HorizontalPlacement = horizontalPlacementFromString( horizontalPlacement ), VerticalPlacement = verticalPlacementFromString( verticalPlacement ), Offset = new Point(xOffset, yOffset), Color = color }; texts.Add(text); } return texts.ToArray(); } /// Converts a string into a horizontal placement enumeration value /// Placement string that will be converted /// The horizontal placement enumeration value matching the string private static Frame.HorizontalTextAlignment horizontalPlacementFromString( string placement ) { switch (placement) { case "left": { return Frame.HorizontalTextAlignment.Left; } case "right": { return Frame.HorizontalTextAlignment.Right; } case "center": default: { return Frame.HorizontalTextAlignment.Center; } } } /// Converts a string into a vertical placement enumeration value /// Placement string that will be converted /// The vertical placement enumeration value matching the string private static Frame.VerticalTextAlignment verticalPlacementFromString( string placement ) { switch (placement) { case "top": { return Frame.VerticalTextAlignment.Top; } case "bottom": { return Frame.VerticalTextAlignment.Bottom; } case "center": default: { return Frame.VerticalTextAlignment.Center; } } } } #endregion // class TextListBuilder /// Loads a skin from the specified path /// Stream containing the skin description private void loadSkin(Stream skinStream) { #if NO_XMLSCHEMA var skinDocument = XDocument.Load(skinStream); #else // Load the schema XmlSchema schema; using (Stream schemaStream = getResourceStream("Resources.skin.xsd")) { schema = XmlHelper.LoadSchema(schemaStream); } // Load the XML document and validate it against the schema XDocument skinDocument = XmlHelper.LoadDocument(schema, skinStream); #endif // The XML document is validated, we don't have to worry about the structure // of the thing anymore, only about the values it provides us with ;) // Load everything contained in the skin and set up our data structures // so we can efficiently render everything loadResources(skinDocument); loadFrames(skinDocument); } /// Loads the resources contained in a skin document /// /// XML document containing a skin description whose resources will be loaded /// private void loadResources(XDocument skinDocument) { // Get the resources node containing a list of all resources of the skin XElement resources = skinDocument.Element("skin").Element("resources"); // Load all fonts used by the skin foreach (XElement element in resources.Descendants("font")) { string fontName = element.Attribute("name").Value; string contentPath = element.Attribute("contentPath").Value; SpriteFont spriteFont = this.contentManager.Load(contentPath); this.fonts.Add(fontName, spriteFont); } // Load all bitmaps used by the skin foreach (XElement element in resources.Descendants("bitmap")) { string bitmapName = element.Attribute("name").Value; string contentPath = element.Attribute("contentPath").Value; Texture2D bitmap = this.contentManager.Load(contentPath); this.bitmaps.Add(bitmapName, bitmap); } } /// Loads the frames contained in a skin document /// /// XML document containing a skin description whose frames will be loaded /// private void loadFrames(XDocument skinDocument) { // Load all the frames specified by the skin XElement resources = skinDocument.Element("skin").Element("frames"); foreach (XElement element in resources.Descendants("frame")) { string name = element.Attribute("name").Value; Frame.Region[] regions = RegionListBuilder.Build(element, this.bitmaps); Frame.Text[] texts = TextListBuilder.Build(element, this.fonts); this.frames.Add(name, new Frame(regions, texts)); } } /// Returns a stream for a resource embedded in this assembly /// Name of the resource for which to get a stream /// A stream for the specified embedded resource private static Stream getResourceStream(string resourceName) { Assembly self = Assembly.GetCallingAssembly(); string[] resources = self.GetManifestResourceNames(); return self.GetManifestResourceStream(typeof(GuiManager), resourceName); } /// Converts a string in the style "#rrggbb" into a Color value /// String containing a hexadecimal color value /// The equivalent color as a Color value private static Color colorFromString(string color) { string trimmedColor = color.Trim(); int startIndex = 0; if (trimmedColor[0] == '#') { ++startIndex; } bool isValidColor = ((trimmedColor.Length - startIndex) == 6) || ((trimmedColor.Length - startIndex) == 8); if (!isValidColor) { throw new ArgumentException("Invalid color format '" + color + "'", "color"); } int r = Convert.ToInt32(trimmedColor.Substring(startIndex + 0, 2), 16); int g = Convert.ToInt32(trimmedColor.Substring(startIndex + 2, 2), 16); int b = Convert.ToInt32(trimmedColor.Substring(startIndex + 4, 2), 16); int a; if ((trimmedColor.Length - startIndex) == 8) { a = Convert.ToInt32(trimmedColor.Substring(startIndex + 6, 2), 16); } else { a = 255; } // No need to worry about overflows: two hexadecimal digits can // by definition not grow larger than 255 ;-) return new Color((byte)r, (byte)g, (byte)b, (byte)a); } } } // namespace Nuclex.UserInterface.Visuals.Flat #endif // !USE_XMLDOCUMENT