#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