-
-
Notifications
You must be signed in to change notification settings - Fork 314
/
GuiInputControl.cs
294 lines (252 loc) · 10.9 KB
/
GuiInputControl.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
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;
}
}
}