mirror of
https://github.com/AlexMacocian/MonoGame.Extended.git
synced 2026-07-24 20:12:23 +00:00
294 lines
11 KiB
C#
294 lines
11 KiB
C#
using System;
|
|
using System.Text;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Input;
|
|
using MonoGame.Extended.Input;
|
|
using MonoGame.Extended.Input.InputListeners;
|
|
|
|
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
|
|
{
|
|
/// <summary>Control through which the user can enter text</summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// 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.
|
|
/// </para>
|
|
/// <para>
|
|
/// 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.
|
|
/// </para>
|
|
/// </remarks>
|
|
public class GuiInputControl : GuiControl, IWritable
|
|
{
|
|
/// <summary>Position of the cursor within the text</summary>
|
|
private int _caretPosition;
|
|
|
|
/// <summary>Tick count at the time the caret was last moved</summary>
|
|
private int _lastCaretMovementTicks;
|
|
|
|
/// <summary>X coordinate of the last known mouse position</summary>
|
|
private float _mouseX;
|
|
|
|
/// <summary>Y coordinate of the last known mouse position</summary>
|
|
private float _mouseY;
|
|
|
|
/// <summary>Array used to store characters before they are appended</summary>
|
|
private readonly char[ /*1*/] _singleCharArray;
|
|
|
|
/// <summary>Text the user has entered into the text input control</summary>
|
|
private readonly StringBuilder _text;
|
|
|
|
/// <summary>Whether user interaction with the control is allowed</summary>
|
|
public bool Enabled;
|
|
|
|
/// <summary>Description to be displayed in the on-screen keyboard</summary>
|
|
public string GuideDescription;
|
|
|
|
/// <summary>Title to be displayed in the on-screen keyboard</summary>
|
|
public string GuideTitle;
|
|
|
|
/// <summary>
|
|
/// Can be set by renderers to enable cursor positioning by the mouse
|
|
/// </summary>
|
|
public IOpeningLocator OpeningLocator;
|
|
|
|
/// <summary>Initializes a new text input control</summary>
|
|
public GuiInputControl()
|
|
{
|
|
_singleCharArray = new char[1];
|
|
_text = new StringBuilder(64);
|
|
|
|
Enabled = true;
|
|
GuideTitle = "Text Entry";
|
|
GuideDescription = "Please enter the text for this input field";
|
|
}
|
|
|
|
/// <summary>Position of the cursor within the text</summary>
|
|
public int CaretPosition
|
|
{
|
|
get { return _caretPosition; }
|
|
set
|
|
{
|
|
if ((value < 0) || (value > Text.Length))
|
|
throw new ArgumentException("Invalid caret position", "CaretPosition");
|
|
|
|
_caretPosition = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>Whether the control currently has the input focus</summary>
|
|
public bool HasFocus => (Screen != null) &&
|
|
ReferenceEquals(Screen.FocusedControl, this);
|
|
|
|
/// <summary>Elapsed milliseconds since the user last moved the caret</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public int MillisecondsSinceLastCaretMovement => Environment.TickCount - _lastCaretMovementTicks;
|
|
|
|
/// <summary>Text that is being displayed on the control</summary>
|
|
public string Text
|
|
{
|
|
get { return _text.ToString(); }
|
|
set
|
|
{
|
|
_text.Remove(0, _text.Length);
|
|
_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 (_caretPosition > _text.Length)
|
|
_caretPosition = _text.Length;
|
|
}
|
|
}
|
|
|
|
/// <summary>Called when the user has entered a character</summary>
|
|
/// <param name="character">Character that has been entered</param>
|
|
void IWritable.OnCharacterEntered(char character)
|
|
{
|
|
OnCharacterEntered(character);
|
|
}
|
|
|
|
/// <summary>Whether the control can currently obtain the input focus</summary>
|
|
bool IFocusable.CanGetFocus => Enabled;
|
|
|
|
/// <summary>Title to be displayed in the on-screen keyboard</summary>
|
|
string IWritable.GuideTitle => GuideTitle;
|
|
|
|
/// <summary>Description to be displayed in the on-screen keyboard</summary>
|
|
string IWritable.GuideDescription => GuideDescription;
|
|
|
|
/// <summary>Called when the user has entered a character</summary>
|
|
/// <param name="character">Character that has been entered</param>
|
|
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;
|
|
_text.Insert(_caretPosition, _singleCharArray);
|
|
++_caretPosition;
|
|
}
|
|
}
|
|
|
|
/// <summary>Called when a key on the keyboard has been pressed down</summary>
|
|
/// <param name="keyCode">Code of the key that was pressed</param>
|
|
/// <returns>
|
|
/// True if the key press was handles by the control, otherwise false.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// If the control indicates that it didn't handle the key press, it will not
|
|
/// receive the associated key release notification.
|
|
/// </remarks>
|
|
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 (_caretPosition > 0)
|
|
{
|
|
UpdateLastCaretMovementTicks();
|
|
|
|
_text.Remove(_caretPosition - 1, 1);
|
|
--_caretPosition;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Delete: erase the character right of the caret
|
|
case Keys.Delete:
|
|
{
|
|
if (_caretPosition < _text.Length)
|
|
{
|
|
UpdateLastCaretMovementTicks();
|
|
|
|
_text.Remove(_caretPosition, 1);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Cursor left: move the caret to the left by one character
|
|
case Keys.Left:
|
|
{
|
|
if (_caretPosition > 0)
|
|
{
|
|
UpdateLastCaretMovementTicks();
|
|
|
|
--_caretPosition;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Cursor right: move the caret to the right by one character
|
|
case Keys.Right:
|
|
{
|
|
if (_caretPosition < _text.Length)
|
|
{
|
|
UpdateLastCaretMovementTicks();
|
|
|
|
++_caretPosition;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Home: place the caret before the first character
|
|
case Keys.Home:
|
|
{
|
|
UpdateLastCaretMovementTicks();
|
|
_caretPosition = 0;
|
|
break;
|
|
}
|
|
|
|
// Home: place the caret behind the last character
|
|
case Keys.End:
|
|
{
|
|
UpdateLastCaretMovementTicks();
|
|
_caretPosition = _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;
|
|
}
|
|
|
|
/// <summary>Called when the mouse position is updated</summary>
|
|
/// <param name="x">X coordinate of the mouse cursor on the control</param>
|
|
/// <param name="y">Y coordinate of the mouse cursor on the control</param>
|
|
protected override void OnMouseMoved(float x, float y)
|
|
{
|
|
_mouseX = x;
|
|
_mouseY = y;
|
|
}
|
|
|
|
/// <summary>Called when a mouse button has been pressed down</summary>
|
|
/// <param name="button">Index of the button that has been pressed</param>
|
|
protected override void OnMousePressed(MouseButton button)
|
|
{
|
|
if (button == MouseButton.Left)
|
|
{
|
|
if (OpeningLocator != null)
|
|
{
|
|
var absoluteBounds = GetAbsoluteBounds();
|
|
var absolutePosition = new Vector2(absoluteBounds.X + _mouseX, absoluteBounds.Y + _mouseY);
|
|
_caretPosition = OpeningLocator.GetClosestOpening(absoluteBounds, Text, absolutePosition);
|
|
}
|
|
else
|
|
{
|
|
// Nope, our renderer is being secretive
|
|
MoveCaretToEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Handles user text input by a physical keyboard</summary>
|
|
/// <param name="character">Character that has been entered</param>
|
|
internal void ProcessCharacter(char character)
|
|
{
|
|
// This notifications always concerns ourselves because it is only sent
|
|
// to the focused control
|
|
OnCharacterEntered(character);
|
|
}
|
|
|
|
/// <summary>Moves the caret to the end of the text</summary>
|
|
private void MoveCaretToEnd()
|
|
{
|
|
UpdateLastCaretMovementTicks();
|
|
_caretPosition = _text.Length;
|
|
}
|
|
|
|
/// <summary>Updates the tick count when the caret was last moved</summary>
|
|
/// <remarks>
|
|
/// Used to prevent the caret from blinking when
|
|
/// </remarks>
|
|
private void UpdateLastCaretMovementTicks()
|
|
{
|
|
_lastCaretMovementTicks = Environment.TickCount;
|
|
}
|
|
}
|
|
} |