#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.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 /// Node of the frame whose regions will be processed private RegionListBuilder(XmlNode frameNode) { this.regionNodes = frameNode.SelectNodes("region"); } /// /// 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( XmlNode frameNode, IDictionary bitmaps ) { RegionListBuilder builder = new RegionListBuilder(frameNode); builder.retrieveBorderSizes(); return builder.createAndPlaceRegions(bitmaps); } /// Retrieves the sizes of the border regions in a frame private void retrieveBorderSizes() { for (int regionIndex = 0; regionIndex < this.regionNodes.Count; ++regionIndex) { // Left and right border width determination string hplacement = this.regionNodes[regionIndex].Attributes["hplacement"].Value; string w = this.regionNodes[regionIndex].Attributes["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 = this.regionNodes[regionIndex].Attributes["vplacement"].Value; string h = this.regionNodes[regionIndex].Attributes["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 /// /// /// Bitmap lookup table to associate a region's bitmap id to the real bitmap /// /// The regions created for the frame private Frame.Region[] createAndPlaceRegions(IDictionary bitmaps) { Frame.Region[] regions = new Frame.Region[this.regionNodes.Count]; // Fill all regions making up the current frame for (int regionIndex = 0; regionIndex < this.regionNodes.Count; ++regionIndex) { // Obtain all attributes of the region node XmlAttribute idAttribute = this.regionNodes[regionIndex].Attributes["id"]; string id = (idAttribute == null) ? null : idAttribute.Value; string source = this.regionNodes[regionIndex].Attributes["source"].Value; string hplacement = this.regionNodes[regionIndex].Attributes["hplacement"].Value; string vplacement = this.regionNodes[regionIndex].Attributes["vplacement"].Value; string x = this.regionNodes[regionIndex].Attributes["x"].Value; string y = this.regionNodes[regionIndex].Attributes["y"].Value; string w = this.regionNodes[regionIndex].Attributes["w"].Value; string h = this.regionNodes[regionIndex].Attributes["h"].Value; // Assign the trivial attributes regions[regionIndex].Id = id; regions[regionIndex].Texture = bitmaps[source]; regions[regionIndex].SourceRegion.X = int.Parse(x); regions[regionIndex].SourceRegion.Y = int.Parse(y); regions[regionIndex].SourceRegion.Width = int.Parse(w); regions[regionIndex].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 regions[regionIndex].DestinationRegion.Location.X, ref regions[regionIndex].DestinationRegion.Size.X ); calculateRegionPlacement( getVerticalPlacementIndex(vplacement), int.Parse(h), this.topBorderWidth, this.bottomBorderWidth, ref regions[regionIndex].DestinationRegion.Location.Y, ref regions[regionIndex].DestinationRegion.Size.Y ); } return regions; } /// /// 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; } } } /// List of the XML nodes for the regions in the current frame private XmlNodeList regionNodes; /// 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( XmlNode frameNode, IDictionary fonts ) { XmlNodeList textNodes = frameNode.SelectNodes("text"); Frame.Text[] texts = new Frame.Text[textNodes.Count]; for (int index = 0; index < textNodes.Count; ++index) { string font = textNodes[index].Attributes["font"].Value; string horizontalPlacement = textNodes[index].Attributes["hplacement"].Value; string verticalPlacement = textNodes[index].Attributes["vplacement"].Value; XmlAttribute xOffsetAttribute = textNodes[index].Attributes["xoffset"]; int xOffset = (xOffsetAttribute == null) ? 0 : int.Parse(xOffsetAttribute.Value); XmlAttribute yOffsetAttribute = textNodes[index].Attributes["yoffset"]; int yOffset = (yOffsetAttribute == null) ? 0 : int.Parse(yOffsetAttribute.Value); XmlAttribute colorAttribute = textNodes[index].Attributes["color"]; Color color; if (colorAttribute == null) { color = Color.White; } else { color = colorFromString(colorAttribute.Value); } texts[index].Font = fonts[font]; texts[index].HorizontalPlacement = horizontalPlacementFromString( horizontalPlacement ); texts[index].VerticalPlacement = verticalPlacementFromString( verticalPlacement ); texts[index].Offset = new Point(xOffset, yOffset); texts[index].Color = color; } return texts; } /// 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) { // 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 XmlDocument skinDocument = XmlHelper.LoadDocument(schema, skinStream); // 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(XmlDocument skinDocument) { // Load the fonts specified in the skin XmlNodeList fonts = skinDocument.SelectNodes("/skin/resources/font"); for (int index = 0; index < fonts.Count; ++index) { string fontName = fonts[index].Attributes["name"].Value; string contentPath = fonts[index].Attributes["contentPath"].Value; SpriteFont spriteFont = this.contentManager.Load(contentPath); this.fonts.Add(fontName, spriteFont); } // Load the bitmaps specified in the skin XmlNodeList bitmaps = skinDocument.SelectNodes("/skin/resources/bitmap"); for (int index = 0; index < bitmaps.Count; ++index) { string bitmapName = bitmaps[index].Attributes["name"].Value; string contentPath = bitmaps[index].Attributes["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(XmlDocument skinDocument) { // Extract all frames from the skin XmlNodeList frames = skinDocument.SelectNodes("/skin/frames/frame"); for (int frameIndex = 0; frameIndex < frames.Count; ++frameIndex) { // Extract the frame's attributes string name = frames[frameIndex].Attributes["name"].Value; Frame.Region[] regions = RegionListBuilder.Build(frames[frameIndex], this.bitmaps); Frame.Text[] texts = TextListBuilder.Build(frames[frameIndex], 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