#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2010 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Nuclex.Input;
using Nuclex.UserInterface.Input;
namespace Nuclex.UserInterface.Controls.Desktop {
/// Control through which the user can enter text
///
///
/// Through this control, users can be asked to enter an arbitrary string
/// of characters, their name for example. Desktop users can enter text through
/// their normal keyboard where Windows' own key translation is used to
/// support regional settings and custom keyboard layouts.
///
///
/// XBox 360 users will open the virtual keyboard when the input box gets
/// the input focus and can add characters by selecting them from the virtual
/// keyboard's character matrix.
///
///
public class InputControl : Control, IWritable {
/// Initializes a new text input control
public InputControl() {
this.singleCharArray = new char[1];
this.text = new StringBuilder(64);
this.Enabled = true;
this.GuideTitle = "Text Entry";
this.GuideDescription = "Please enter the text for this input field";
}
/// Text that is being displayed on the control
public string Text {
get { return this.text.ToString(); }
set {
this.text.Remove(0, this.text.Length);
this.text.Append(value);
// Cursor index is in openings between letters, including before first
// and after last letter, so text.Length is a valid position.
if(this.caretPosition > this.text.Length) {
this.caretPosition = this.text.Length;
}
}
}
/// Position of the cursor within the text
public int CaretPosition {
get { return this.caretPosition; }
set {
if((value < 0) || (value > this.Text.Length)) {
throw new ArgumentException("Invalid caret position", "CaretPosition");
}
this.caretPosition = value;
}
}
/// Whether the control currently has the input focus
public bool HasFocus {
get {
return
(Screen != null) &&
ReferenceEquals(Screen.FocusedControl, this);
}
}
/// Elapsed milliseconds since the user last moved the caret
///
/// This is an unusual property for an input box to have. It is retrieved by
/// the renderer and could be used for several purposes, such as lighting up
/// a control when text is entered to provide better visual tracking or
/// preventing the cursor from blinking whilst the user is typing.
///
public int MillisecondsSinceLastCaretMovement {
get { return Environment.TickCount - this.lastCaretMovementTicks; }
}
/// Called when the user has entered a character
/// Character that has been entered
protected virtual void OnCharacterEntered(char character) {
// For some reason, Windows translates Backspace to a character :)
if(character != '\b') {
updateLastCaretMovementTicks();
// There's no single-character overload on the XBox 360...
singleCharArray[0] = character;
this.text.Insert(this.caretPosition, singleCharArray);
++this.caretPosition;
}
}
/// Called when a key on the keyboard has been pressed down
/// Code of the key that was pressed
///
/// True if the key press was handles by the control, otherwise false.
///
///
/// If the control indicates that it didn't handle the key press, it will not
/// receive the associated key release notification.
///
protected override bool OnKeyPressed(Keys keyCode) {
// We only accept keys if we have the focus. If the notification is sent in search
// for a key handler without the input box being focused, we will not respond to
// the key press in order to not sabotage shortcut keys for other controls.
if(!HasFocus) {
return false;
}
switch(keyCode) {
// Backspace: erase the character left of the caret
case Keys.Back: {
if(this.caretPosition > 0) {
updateLastCaretMovementTicks();
this.text.Remove(this.caretPosition - 1, 1);
--this.caretPosition;
}
break;
}
// Delete: erase the character right of the caret
case Keys.Delete: {
if(this.caretPosition < text.Length) {
updateLastCaretMovementTicks();
this.text.Remove(this.caretPosition, 1);
}
break;
}
// Cursor left: move the caret to the left by one character
case Keys.Left: {
if(this.caretPosition > 0) {
updateLastCaretMovementTicks();
--this.caretPosition;
}
break;
}
// Cursor right: move the caret to the right by one character
case Keys.Right: {
if(this.caretPosition < this.text.Length) {
updateLastCaretMovementTicks();
++this.caretPosition;
}
break;
}
// Home: place the caret before the first character
case Keys.Home: {
updateLastCaretMovementTicks();
this.caretPosition = 0;
break;
}
// Home: place the caret behind the last character
case Keys.End: {
updateLastCaretMovementTicks();
this.caretPosition = this.text.Length;
break;
}
// Keys that can be used to navigate the dialog
case Keys.Tab:
case Keys.Up:
case Keys.Down:
case Keys.Enter: {
return false;
}
}
return true;
}
/// Called when the mouse position is updated
/// X coordinate of the mouse cursor on the control
/// Y coordinate of the mouse cursor on the control
protected override void OnMouseMoved(float x, float y) {
this.mouseX = x;
this.mouseY = y;
}
/// Called when a mouse button has been pressed down
/// Index of the button that has been pressed
protected override void OnMousePressed(MouseButtons button) {
if(button == MouseButtons.Left) {
// If the renderer was so nice to provide an OpeningLocator for us,
// we can locate exactly which opening was closest to the position
// the user has clicked at and place the caret accordingly
if(this.OpeningLocator != null) {
RectangleF absoluteBounds = GetAbsoluteBounds();
Vector2 absolutePosition = new Vector2(
absoluteBounds.X + this.mouseX,
absoluteBounds.Y + this.mouseY
);
this.caretPosition = this.OpeningLocator.GetClosestOpening(
absoluteBounds, Text, absolutePosition
);
} else { // Nope, our renderer is being secretive
moveCaretToEnd();
}
}
}
/// Handles user text input by a physical keyboard
/// Character that has been entered
internal void ProcessCharacter(char character) {
// This notifications always concerns ourselves because it is only sent
// to the focused control
OnCharacterEntered(character);
}
/// Called when the user has entered a character
/// Character that has been entered
void IWritable.OnCharacterEntered(char character) {
OnCharacterEntered(character);
}
/// Whether the control can currently obtain the input focus
bool IFocusable.CanGetFocus {
get { return this.Enabled; }
}
/// Title to be displayed in the on-screen keyboard
string IWritable.GuideTitle {
get { return this.GuideTitle; }
}
/// Description to be displayed in the on-screen keyboard
string IWritable.GuideDescription {
get { return this.GuideDescription; }
}
/// Moves the caret to the end of the text
private void moveCaretToEnd() {
updateLastCaretMovementTicks();
this.caretPosition = this.text.Length;
}
/// Updates the tick count when the caret was last moved
///
/// Used to prevent the caret from blinking when
///
private void updateLastCaretMovementTicks() {
this.lastCaretMovementTicks = Environment.TickCount;
}
/// Title to be displayed in the on-screen keyboard
public string GuideTitle;
/// Description to be displayed in the on-screen keyboard
public string GuideDescription;
/// Whether user interaction with the control is allowed
public bool Enabled;
///
/// Can be set by renderers to enable cursor positioning by the mouse
///
public IOpeningLocator OpeningLocator;
/// Array used to store characters before they are appended
private char[/*1*/] singleCharArray;
/// Tick count at the time the caret was last moved
private int lastCaretMovementTicks;
/// Text the user has entered into the text input control
private StringBuilder text;
/// Position of the cursor within the text
private int caretPosition;
/// X coordinate of the last known mouse position
private float mouseX;
/// Y coordinate of the last known mouse position
private float mouseY;
}
} // namespace Nuclex.UserInterface.Controls.Desktop