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; } }