#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