using System;
using Chernobyl.Collections.Generic.Event;
using Chernobyl.Dependency;
using Chernobyl.Event;
using Chernobyl.Graphics.Drawing;
using Chernobyl.Graphics.Writing;
using Chernobyl.Mathematics.Collision;
using Chernobyl.Mathematics.Geometry;
using Chernobyl.Mathematics.Movement;
using Chernobyl.Resources;
using Microsoft.Xna.Framework.Graphics;
namespace Chernobyl.Graphics.Xna.Writing
{
///
/// A representation of XNA text that is drawn to the screen.
///
public class XnaText : DrawableTransformCollidable2D, IText
{
///
/// Constructor.
///
/// The services instance that will give its
/// services to this instance and/or receive services from this instance.
/// The font to apply to this XNA text.
public XnaText(IEventCollection services, IFont font)
{
_drawableImplementer = new Drawable();
_decoratedTransform = new MatrixTransform();
_maximumDrawLength = int.MaxValue;
services.Inject(this);
if (font != null)
Font = font;
else
Resource.FromNamed(services, FontProcessedCallback, "default");
Rectangle rectangle = new Rectangle();
CollisionVolume = rectangle;
Transform.MakeParentChild(this, rectangle);
ViewportHeight = SpriteBatch.GraphicsDevice.Viewport.Height;
}
///
/// Called when this drawable should be drawn.
///
public override uint Draw()
{
SpriteBatch.Begin();
// we want the text to be positioned relative to it's center to match
// the standard set by other Chernobyl 2D objects. To do this, we subtract
// half the width and height from the text's position. We also want
// the text to be positioned in the Y relative to the bottom of the
// screen (again, as is standard in Chernobyl). To do this, we subtract
// the position's Y from the viewport's height.
Microsoft.Xna.Framework.Vector2 pos = new Microsoft.Xna.Framework.Vector2();
float halfWidth = CollisionVolume.Width * 0.5f;
float halfHeight = CollisionVolume.Height * 0.5f;
pos.X = Position.X - halfWidth;
pos.Y = (ViewportHeight - Position.Y) - halfHeight;
SpriteBatch.DrawString(XnaFont, DrawnString, pos, _color);
SpriteBatch.End();
return base.Draw();
}
///
/// An event handler that is invoked after the
/// property has been changed.
///
public event EventHandler> WritingChanged;
///
/// The text that is rendered to the screen.
///
public string Writing
{
get { return _writing; }
set
{
if (WritingChanged != null)
{
ValueChangedEventArgs vce = new ValueChangedEventArgs(_writing, value);
_writing = value;
DrawnStringDirty = true;
WritingChanged(this, vce);
}
else
{
_writing = value;
DrawnStringDirty = true;
}
}
}
///
/// The zero based index at which drawing of this text starts from. For
/// example, if the is set to "Hello!" and the
/// property is set to 2 then the text that
/// would be drawn would be "llo!". The default value of this property is
/// zero.
///
public int MinimumDrawIndex
{
get { return _minimumDrawIndex; }
set
{
if (value < 0)
throw new ArgumentException("The MinimumDrawIndex cannot be " +
"less than zero. You specified: " + value, "value");
if (MinimumDrawIndexChanged != null)
{
ValueChangedEventArgs vce = new ValueChangedEventArgs(_minimumDrawIndex, value);
_minimumDrawIndex = value;
DrawnStringDirty = true;
MinimumDrawIndexChanged(this, vce);
}
else
{
_minimumDrawIndex = value;
DrawnStringDirty = true;
}
}
}
///
/// The maximum number of characters in the text to draw. For example,
/// if the is set to "Hello!" and the
/// property is set to 3 then the text that
/// would be drawn would be "Hel". The default value of this property is
/// .
///
public int MaximumDrawLength
{
get { return _maximumDrawLength; }
set
{
if (value < 0)
throw new ArgumentException("The MaximumDrawLength cannot be " +
"less than zero. You specified: " + value, "value");
if (MaximumDrawLengthChanged != null)
{
ValueChangedEventArgs vce = new ValueChangedEventArgs(_maximumDrawLength, value);
_maximumDrawLength = value;
DrawnStringDirty = true;
MaximumDrawLengthChanged(this, vce);
}
else
{
_maximumDrawLength = value;
DrawnStringDirty = true;
}
}
}
///
/// An event that is raised right after the
/// property has changed.
///
public event EventHandler> MinimumDrawIndexChanged;
///
/// An event that is raised right after the
/// property has changed.
///
public event EventHandler> MaximumDrawLengthChanged;
///
/// The associated with this text.
///
public IFont Font
{
get { return _font; }
set
{
_font = value;
// get the XNA font
XnaFont xnaFont = null;
IFont nextFont = _font;
while (xnaFont == null)
{
xnaFont = nextFont as XnaFont;
nextFont = nextFont.Controller;
}
XnaFont = xnaFont.XnaSpriteFont;
}
}
///
/// The color of the rendered text.
///
public Color Color { get { return Utility.ToChernobylColor(_color); } set { _color = Utility.ToXnaColor(value); } }
///
/// The sprite batch to render the text with.
///
[Inject]
public SpriteBatch SpriteBatch { get; set; }
///
/// The IText instance that is used to control the text created by a
/// graphics API like XNA, OpenGL, DirectX, etc. This property is null,
/// by default, for the XnaText.
///
public IText Controller { get; protected set; }
///
/// The collidable that implements the collision of this object.
///
protected override ICollidable2D CollidableImplementer { get { return CollisionVolume; } }
///
/// The drawable that implements the drawing of this object.
///
protected override IDrawable DrawableImplementer { get { return _drawableImplementer; } }
///
/// The transform that provides this transforms capabilities.
///
protected override ITransform DecoratedTransform { get { return _decoratedTransform; } }
///
/// The that implements the
/// interface for this object.
///
protected override IShape ShapeImplementer { get { return CollisionVolume; } }
///
/// The XNA SpriteFont used to render this text.
///
SpriteFont XnaFont { get; set; }
///
/// The height of the viewport that we are rendering to. The viewport
/// height is used to ensure the Y position of the text is relative to
/// the bottom. In XNA, it is relative to the top so we subtract the
/// text's Y position from the height to make it relative to the bottom.
///
float ViewportHeight { get; set; }
///
/// Grabs the latest version of the from the
/// string and then calls .
///
void RetakeDrawnString()
{
int maxDrawLength = Math.Min(MaximumDrawLength, Writing.Length - MinimumDrawIndex);
int minDrawIndex = Math.Min(MinimumDrawIndex, Writing.Length);
if (minDrawIndex == Writing.Length || maxDrawLength == 0)
DrawnString = String.Empty;
else
DrawnString = Writing.Substring(minDrawIndex, maxDrawLength);
// collision shape needs to be recalculated
CollisionVolumeDirty = true;
// the drawn string is now up to date
DrawnStringDirty = false;
}
///
/// Remeasures the writing of this text and applies the new measurements
/// to the collision bounds of this text.
///
void RemeasureWritingBounds()
{
// don't measure anything if we haven't been initialized
if(_writing != null && Font != null)
{
Microsoft.Xna.Framework.Vector2 textSize = XnaFont.MeasureString(DrawnString);
Mathematics.Vector2 chernobylTextSize;
Utility.ToChernobylVector(out chernobylTextSize, ref textSize);
_collidableImplementer.SetWidthHeight(chernobylTextSize.X, chernobylTextSize.Y);
// the collision shape has been recalculated
CollisionVolumeDirty = false;
}
}
///
/// Invoked when the font is properly loaded.
///
/// The result of the font processing.
void FontProcessedCallback(IResourceProcessResult result)
{
Font = result.Resource;
}
///
/// The Rectangle that implements the collision for this object.
///
Rectangle CollisionVolume
{
get
{
if(CollisionVolumeDirty == true)
RemeasureWritingBounds();
return _collidableImplementer;
}
set { _collidableImplementer = value; }
}
///
/// The string that is drawn for this instance. This
/// string represents the string as the following:
///
/// DrawnString = Writing.Substring(MinimumDrawIndex, MaximumDrawLength);
///
///
string DrawnString
{
get
{
if (DrawnStringDirty == true)
RetakeDrawnString();
return _drawnString;
}
set { _drawnString = value; }
}
///
/// True if the needs to be retaken, false if
/// otherwise.
///
bool DrawnStringDirty
{
get { return _drawnStringDirty; }
set { CollisionVolumeDirty = _drawnStringDirty = value; }
}
///
/// True if the needs to be recalculated,
/// false if otherwise.
///
bool CollisionVolumeDirty { get; set; }
///
/// The backing field to .
///
Microsoft.Xna.Framework.Graphics.Color _color;
///
/// The backing field to .
///
string _writing;
///
/// The backing field to .
///
Rectangle _collidableImplementer;
///
/// The backing field to .
///
IFont _font;
///
/// The backing field to .
///
int _minimumDrawIndex;
///
/// The backing field to .
///
int _maximumDrawLength;
///
/// The backing field to .
///
bool _drawnStringDirty;
///
/// The backing field to .
///
string _drawnString;
///
/// The backing field to .
///
IDrawable _drawableImplementer;
///
/// The backing field to .
///
ITransform _decoratedTransform;
}
}