using System.Collections.Generic;
using System.Linq;
using Chernobyl.Collections.Generic.Event;
using Chernobyl.DesignPatterns.Extension;
using Chernobyl.Event;
using Chernobyl.Graphics.Writing;
using Chernobyl.Mathematics;
using Chernobyl.Mathematics.Geometry;
using Chernobyl.Mathematics.Vectors;
namespace Chernobyl.Interface.Writing
{
///
/// An that can be applied to
/// instances. This extension causes the text of the
/// extended instance to only be displayed within the
/// constraints of the passed in.
///
public class TextConstraint : Extension
{
///
/// Initializes a new instance of the class.
///
/// The instance that
/// takes and gives out services.
/// The to constrain
/// the text of the extended instance to.
public TextConstraint(IEventCollection services, IShape constrainTo)
: this(services, constrainTo, null)
{ }
///
/// Initializes a new instance of the class.
///
/// The instance that
/// takes and gives out services.
/// The to constrain
/// the text of the extended instance to.
/// The instance to extend or
/// null if the instance will be attached to ( )
/// later on.
public TextConstraint(IEventCollection services, IShape constrainTo, IText extended)
{
Services = services;
IsTextCursorFollowingEnabled = DefaultIsTextCursorFollowingEnabledValue;
ConstrainTo = constrainTo;
Extended = extended;
}
///
/// Attaches this extension to the passed in object so that is can be
/// extended.
///
/// The object to extend.
protected override void AttachTo(IText extended)
{
if (IsTextCursorFollowingEnabled == true && TextCursor == null)
{
IEnumerable textCursors = extended.Extensions.OfType();
if (textCursors.Count() != 0)
TextCursor = textCursors.First();
else
TextCursor = new TextCursor(Services, extended);
TextCursor.IndexLocationChanged += TextCursor_IndexLocationChanged;
}
extended.Extensions.Add(this);
extended.WritingChanged += extended_WritingChanged;
RepositionText();
}
///
/// Removes the decorator from an object so that it is no longer extended.
///
/// The object to remove the extension from.
protected override void DetachFrom(IText extended)
{
extended.WritingChanged -= extended_WritingChanged;
extended.Extensions.Remove(this);
if (TextCursor != null)
TextCursor.IndexLocationChanged -= TextCursor_IndexLocationChanged;
}
///
/// An event handler for the event of
/// the ( )
/// instance.
///
/// The source of the event.
/// The
/// instance containing the event data.
void extended_WritingChanged(object sender, ValueChangedEventArgs e)
{
RepositionText();
}
void TextCursor_IndexLocationChanged(object sender, ValueChangedEventArgs e)
{
IText text = Extended;
// first get the displaying string up to the text cursor's index
// then grab it's width; this width will be the distance from the
// text's left side to the cursor
if(e.NewValue > e.OldValue)
{
string textToCursor = text.Writing.Substring(text.MinimumDrawIndex, e.NewValue - text.MinimumDrawIndex);
float textCursorsDistanceFromTextsLeftSide = text.Font.MeasureString(textToCursor).X;
// if the distance from the left side of the text to the cursor is
// larger than the constraint, we will need to move the displaying
// text to the left to ensure the cursor stays within the bounds of
// the constraint.
if (textCursorsDistanceFromTextsLeftSide >= ConstrainTo.Width)
{
// remove one character from the front of the text's writing until
// it is of the correct size. As we do so, increase the minimum
// index to "move" the displaying to the left.
float newWidth = 0.0f;
int minDrawIndex = text.MinimumDrawIndex;
do
{
minDrawIndex++;
textToCursor = textToCursor.Remove(0, 1);
newWidth = text.Font.MeasureString(textToCursor).X;
} while (newWidth > ConstrainTo.Width);
text.MaximumDrawLength = textToCursor.Length;
text.MinimumDrawIndex = minDrawIndex;
}
}
else
{
if (text.MinimumDrawIndex != 0)
text.MinimumDrawIndex--;
}
}
///
/// Repositions the so that it fits within the
/// rectangle constraint.
///
void RepositionText()
{
IText text = Extended;
// check if the width of the text needs is larger than the constraint
if(IsTextCursorFollowingEnabled == false)
{
if (text.Width > ConstrainTo.Width)
{
// remove one character from the end of the text's writing until
// it is of the correct size.
string writing = text.Writing;
float newWidth = 0.0f;
do
{
writing = writing.Remove(writing.Length - 1, 1);
newWidth = text.Font.MeasureString(writing).X;
} while (newWidth > ConstrainTo.Width);
text.MaximumDrawLength = writing.Length;
}
}
// snap to the edge of the text to the left edge of the constraint
// grab the text's distance from the left edge of the constraint
float distance = (text.Width * 0.5f) - (ConstrainTo.Width * 0.5f);
// now create a matrix that will pin the text instance to the left
// edge of the constraint
Matrix4 local = text.LocalMatrix;
local.Row3 = new Vector4(distance, local.Row3.Y, local.Row3.Z, local.Row3.W);
text.SetTransformation(ref local);
}
///
/// The instance to follow with regard to text
/// being viewed. If this property is set to null then TextCursor
/// following is turned off. By default,
/// instances will attempt to locate a within
/// the list of the extended
/// instance and use it; if none is available,
/// an instance will automatically be attached
/// to the extended instance. Note that, regardless
/// of the value of this property, if the
/// property is false, then
/// following is disabled.
///
public TextCursor TextCursor { get; set; }
///
/// True if following (see the
/// property for more information) is enabled,
/// false if otherwise. If this property is true, the
/// property is null, and there is no extension
/// on the extended instance, instances of this class
/// will automatically add a instance to the
/// extended instance. By default, the default value
/// of this property is the value of the
/// field.
///
public bool IsTextCursorFollowingEnabled { get; set; }
///
/// The default value (true) of the
/// property.
///
public bool DefaultIsTextCursorFollowingEnabledValue = true;
///
/// The to constrain the text of the extended
/// instance to.
///
IShape ConstrainTo { get; set; }
///
/// The instance that takes and gives
/// out services.
///
IEventCollection Services { get; set; }
}
}