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