#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