#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