using System;
using System.Timers;
using Chernobyl.Collections.Generic;
using Chernobyl.Collections.Generic.Event;
using Chernobyl.Creation;
using Chernobyl.Dependency;
using Chernobyl.DesignPatterns.Extension;
using Chernobyl.Event;
using Chernobyl.Graphics;
using Chernobyl.Graphics.Drawing;
using Chernobyl.Graphics.Polygon;
using Chernobyl.Graphics.Polygon.Buffers;
using Chernobyl.Graphics.Writing;
using Chernobyl.Input.Controls;
using Chernobyl.Mathematics;
using Chernobyl.Mathematics.Geometry;
using Chernobyl.Mathematics.Movement;
using Chernobyl.Mathematics.Vectors;
using Chernobyl.Switch;
using Chernobyl.Utility;
namespace Chernobyl.Interface.Writing
{
///
/// Places a text cursor (aka, a caret) on an instance
/// and allows that cursor to be moved using the keyboard keys.
///
public class TextCursor : Extension
{
///
/// Initializes a new instance of the class.
///
/// The
/// instance that takes and gives out services.
public TextCursor(IEventCollection services)
: this(services, null)
{ }
///
/// Initializes a new instance of the class and
/// attaches this to the passed in
/// .
///
/// The
/// instance that takes and gives out services.
/// The to attach to or null
/// if no should be attached to right now.
public TextCursor(IEventCollection services, IText extended)
{
_cursorFlashRateInMilliseconds = DefaultCursorFlashRateInMilliseconds;
Services = services;
LeftRepeater = new Repeater(100);
RightRepeater = new Repeater(100);
Extended = extended;
}
///
/// An event handler that can be assigned to an event. This method moves
/// this text cursor left. By default, it is assigned to a
/// which is attached to the left arrow keyboard
/// key.
///
/// The sender.
/// The instance containing
/// the event data.
public void MoveLeft(object sender, EventArgs e)
{
if(IndexLocation != Extended.MinimumDrawIndex)
IndexLocation--;
}
///
/// An event handler that can be assigned to an event. This method moves
/// this text cursor right. By default, it is assigned to a
/// which is attached to the right arrow keyboard
/// key.
///
/// The sender.
/// The instance containing
/// the event data.
public void MoveRight(object sender, EventArgs e)
{
if (IndexLocation != Extended.Writing.Length)
IndexLocation++;
}
///
/// An event handler that can be assigned to an event. This method moves
/// this text cursor to the beginning of the text. By default, it is
/// assigned to the home keyboard key.
///
/// The sender.
/// The instance containing
/// the event data.
public void MoveToBeginning(object sender, EventArgs e)
{
IndexLocation = 0;
}
///
/// An event handler that can be assigned to an event. This method moves
/// this text cursor to the end of the text. By default, it is assigned
/// to the end keyboard key.
///
/// The sender.
/// The instance containing
/// the event data.
public void MoveToEnd(object sender, EventArgs e)
{
IndexLocation = Extended.Writing.Length;
}
///
/// The index (zero based) location of the text cursor in the string of
/// the attached . The cursor is placed right before
/// this index location.
///
public int IndexLocation
{
get { return _indexLocation; }
set
{
if(IndexLocationChanged != null)
{
ValueChangedEventArgs vce = new ValueChangedEventArgs(_indexLocation, value);
_indexLocation = value;
TextCursorRender.IsUpdateNeeded = true;
IndexLocationChanged(this, vce);
}
else
{
_indexLocation = value;
TextCursorRender.IsUpdateNeeded = true;
}
}
}
///
/// An event that is raised after the
/// property has changed.
///
public event EventHandler> IndexLocationChanged;
///
/// The instance that this
/// gets it's keyboard events from.
///
[Inject]
public IKeyboard Keyboard
{
get { return _keyboard; }
set
{
// if we have a previous keyboard we are going to need to un-assign
// ourself from it's events and assign ourself to the events from
// the newest keyboard. Otherwise, we will wait till we've
// attached to an IText before we assign ourselves to keyboard
// events
if (Extended != null)
{
if (_keyboard != null)
UnassignFromKeyboardEvents();
_keyboard = value;
AssignToKeyboardEvents();
}
else
_keyboard = value;
}
}
///
/// The used as the default Chernobyl
/// for rendering.
///
[Inject]
public Scene DefaultScene
{
set
{
_sceneDrawLocation = value.Draw2D;
// If the DrawBuffer is ready and does not have a parent, parent
// it.
if (DrawBuffer != null && DrawBuffer.DrawableParent == null)
DrawBuffer.DrawableParent = _sceneDrawLocation;
}
}
///
/// The amount of time (in milliseconds) it takes for the cursor to flash
/// on and off.
///
/// The default value for this property is the value of the
/// field.
public static int CursorFlashRateInMilliseconds
{
get { return _cursorFlashRateInMilliseconds; }
set
{
_cursorFlashRateInMilliseconds = value;
// if the flashing timer has already been created, we need to
// recreate it
if (FlashingTimer == null)
FlashingTimer = new Timer(_cursorFlashRateInMilliseconds);
}
}
///
/// The default value of the
/// property.
///
public static int DefaultCursorFlashRateInMilliseconds = 600;
///
/// Attaches this extension to the passed in object so that is can be
/// extended.
///
/// The object to extend.
protected override void AttachTo(IText extended)
{
extended.Extensions.Add(this);
extended.WritingChanged += extended_WritingChanged;
extended.MinimumDrawIndexChanged += extended_MinimumDrawIndexChanged;
extended.MaximumDrawLengthChanged += extended_MaximumDrawLengthChanged;
if (TextCursorDrawParent == null)
{
Services.Create, IBuffer>>(
(sender, e) => DrawBuffer = e.CreatedInstance,
new Pair, IBuffer>(Services, VertexColor.CreateSquare(Services, ColorRgba.Black)));
// If the DrawBuffer is ready and does not have a parent, parent
// it.
if (_sceneDrawLocation != null && DrawBuffer != null &&
DrawBuffer.DrawableParent == null)
DrawBuffer.DrawableParent = _sceneDrawLocation;
// create the flashing timer, the "set" of the FlashingTimer
// property will properly configure the flashing timer and
// OnOffDrawable
if (FlashingTimer == null)
FlashingTimer = new Timer(CursorFlashRateInMilliseconds);
}
// create a TransformRectangle of 1x1. The transform rectangle will
// automatically adjust as we scale the sprite. Currently, the sprite
// is using a 1x1 square mesh element to render textures to, therefore,
// a 1x1 TransformRectangle will only work properly with a 1x1 mesh
// element square.
Rectangle rectangle = new Rectangle(-0.5f, -0.5f, 1, 1);
Transform.MakeParentChild(extended, rectangle);
// create a render for this instance
TextCursorRender = new Render(Services, PrimitiveType.TriangleStrip, null, new TextCursorTransform(this), rectangle);
TextCursorRender.DrawableParent = TextCursorDrawParent;
Transform.MakeParentChild(extended, TextCursorRender);
// scale the render so that it is the same height as the text we are
// a cursor of. Also, place the cursor at the beginning of the line
// of text by default
TextCursorRender.ScaleY(extended.Height);
IndexLocation = extended.Writing.Length;
TextCursorRender.IsUpdateNeeded = true;
AssignToKeyboardEvents();
}
///
/// 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)
{
TextCursorDrawParent.DrawableChildren.Remove(TextCursorRender);
Transform.SeperateParentChild(extended, TextCursorRender);
extended.Extensions.Remove(this);
extended.WritingChanged -= extended_WritingChanged;
UnassignFromKeyboardEvents();
}
///
/// Un-assigns an instance of this class to the necessary events from the
/// .
///
void UnassignFromKeyboardEvents()
{
// setup the left arrow key
Keyboard.Left.OnDown -= LeftRepeater.Start;
Keyboard.Left.OnUp -= LeftRepeater.Stop;
LeftRepeater.Elapsed -= MoveLeft;
LeftRepeater.SwitchedOn -= MoveLeft;
// setup the left arrow key
Keyboard.Right.OnDown -= RightRepeater.Start;
Keyboard.Right.OnUp -= RightRepeater.Stop;
RightRepeater.Elapsed -= MoveRight;
RightRepeater.SwitchedOn -= MoveRight;
// setup the home and end keys
Keyboard.Home.OnDown -= MoveToBeginning;
Keyboard.End.OnDown -= MoveToEnd;
}
///
/// Assigns an instance of this class to the necessary events from the
/// .
///
void AssignToKeyboardEvents()
{
// setup the left arrow key
Keyboard.Left.OnDown += LeftRepeater.Start;
Keyboard.Left.OnUp += LeftRepeater.Stop;
LeftRepeater.Elapsed += MoveLeft;
LeftRepeater.SwitchedOn += MoveLeft;
// setup the left arrow key
Keyboard.Right.OnDown += RightRepeater.Start;
Keyboard.Right.OnUp += RightRepeater.Stop;
RightRepeater.Elapsed += MoveRight;
RightRepeater.SwitchedOn += MoveRight;
// setup the home and end keys
Keyboard.Home.OnDown += MoveToBeginning;
Keyboard.End.OnDown += MoveToEnd;
}
///
/// Handles the event on the attached
/// instance. This method dirties the matrix of the
/// so that it will recalculate it's position.
///
/// The source of the event.
/// The event data.
void extended_WritingChanged(object sender, ValueChangedEventArgs e)
{
TextCursorRender.IsUpdateNeeded = true;
}
///
/// Handles the MaximumDrawLengthChanged event of the extended control.
/// This method dirties the matrix of the
/// so that it will recalculate it's position.
///
/// The source of the event.
/// The instance
/// containing the event data.
void extended_MaximumDrawLengthChanged(object sender, ValueChangedEventArgs e)
{
TextCursorRender.IsUpdateNeeded = true;
}
///
/// Handles the MinimumDrawIndexChanged event of the extended control.
/// This method dirties the matrix of the
/// so that it will recalculate it's position.
///
/// The source of the event.
/// The instance
/// containing the event data.
void extended_MinimumDrawIndexChanged(object sender, ValueChangedEventArgs e)
{
TextCursorRender.IsUpdateNeeded = true;
}
///
/// The instance used to render and transform the
/// text cursor.
///
IRender TextCursorRender { get; set; }
///
/// The used to control the repeat rates of
/// several keys.
///
Repeater LeftRepeater { get; set; }
///
/// The used to control the repeat rates of
/// several keys.
///
Repeater RightRepeater { get; set; }
///
/// The that was given to this class in its
/// constructor. When a new instance is set on
/// this property the class will call
///
/// passing in 'this' instance.
///
IEventCollection Services
{
get { return _services; }
set
{
_services = value;
_services.Inject(this);
}
}
///
/// The parent that the text cursor
/// s attach to.
///
static IDrawable TextCursorDrawParent { get { return OnOffDrawable; } }
///
/// The instance that renders the cursor geometry to the screen.
///
static IDrawBuffer DrawBuffer
{
get { return _drawBuffer; }
set
{
value.ThrowIfNull("value");
IDrawBuffer previousValue = _drawBuffer;
_drawBuffer = value;
if(previousValue != null)
{
if (OnOffDrawable.DrawableParent == previousValue)
OnOffDrawable.DrawableParent = null;
}
OnOffDrawable.DrawableParent = _drawBuffer;
}
}
///
/// The used to turn off the
/// cursor instances.
///
static OnOffDrawable OnOffDrawable
{
get { return _onOffDrawable ?? (_onOffDrawable = new OnOffDrawable((IDrawable)null)); }
}
///
/// The timer that is used to generate the flashing cursor. Setting this
/// property will cause the to no longer
/// listen to the previous FlashingTimer's
/// event and start listening to the event of the new instance. Also,
/// the new timer will be started using .
///
static Timer FlashingTimer
{
get { return _flashingTimer; }
set
{
if (_flashingTimer != null)
_flashingTimer.Elapsed -= OnOffDrawable.Toggle;
_flashingTimer = value;
_flashingTimer.Elapsed += OnOffDrawable.Toggle;
_flashingTimer.Start();
}
}
///
/// An specifically made to move the cursor
/// around some text.
///
class TextCursorTransform : RemoveScaleTransform
{
///
/// Initializes a new instance of the
/// class.
///
/// The text cursor.
public TextCursorTransform(TextCursor textCursor)
{
TextCursor = new WeakReference(textCursor);
}
///
/// Orders this transform so that it's world matrix represents it's
/// local matrix relative to it's parent's world matrix.
///
protected override void Update()
{
if (IsUpdateNeeded == true)
{
TextCursor textCursor = TextCursor.Target;
IText text = textCursor.Extended;
string writing = text.Writing;
// if the location is past the end of the text, then we'll slap the
// cursor on the end. Don't set the length using "IndexLocation"
// though since the "set" of that property calls this method.
if (textCursor.IndexLocation > writing.Length)
textCursor.IndexLocation = writing.Length;
// measure the distance from the beginning of the string to the
// location of the cursor and then use that distance to get the
// distance from the center of the string (transform) to the
// cursor's location
float stringWidth = (text.Font.MeasureString(writing.Substring(text.MinimumDrawIndex, textCursor.IndexLocation - text.MinimumDrawIndex))).X;
float distanceFromCenter = stringWidth - (text.Width * 0.5f);
// now place the reposition the transform of the cursor instance.
Vector4 row3 = LocalMatrix.Row3;
Matrix4 transform = LocalMatrix;
transform.Row3 = new Vector4(distanceFromCenter, row3.Y, row3.Z, row3.W);
SetTransformation(ref transform);
base.Update();
}
}
///
/// The wrapped in a
/// to prevent cyclical referencing (allows instances to be garbage
/// collected).
///
WeakReference TextCursor { get; set; }
}
///
/// The backing field to .
///
static IDrawBuffer _drawBuffer;
///
/// The backing field to .
///
static OnOffDrawable _onOffDrawable;
///
/// The backing field to .
///
static int _cursorFlashRateInMilliseconds;
///
/// The backing field to .
///
static Timer _flashingTimer;
///
/// The backing field to .
///
IEventCollection _services;
///
/// The backing field to .
///
int _indexLocation;
///
/// The backing field to .
///
IKeyboard _keyboard;
///
/// The that represents the location in the scene
/// where the cursor should be drawn.
///
IDrawable _sceneDrawLocation;
}
}