PageRenderTime 147ms CodeModel.GetById 80ms app.highlight 18ms RepoModel.GetById 43ms app.codeStats 0ms

/Scenes/UserInterfaces/Controls/TextBox.cs

#
C# | 653 lines | 360 code | 72 blank | 221 comment | 28 complexity | 215d8c071557ad2164189df66ac0ab04 MD5 | raw file
  1using System;
  2using System.IO;
  3using Delta.Engine;
  4using Delta.InputSystem;
  5using Delta.Rendering.Basics.Drawing;
  6using Delta.Scenes.Enums;
  7using Delta.Utilities;
  8using Delta.Utilities.Datatypes;
  9using Delta.Utilities.Datatypes.Advanced;
 10using Delta.Utilities.Helpers;
 11using NUnit.Framework;
 12
 13namespace Delta.Scenes.UserInterfaces.Controls
 14{
 15	/// <summary>
 16	/// This class represents a simple (single-line) Text box control which
 17	/// allows the user doing input like e.g. to enter his name and confirm it by
 18	/// an the 'Enter' key.
 19	/// </summary>
 20	public class TextBox : Label
 21	{
 22		#region TextCursor Class
 23		/// <summary>
 24		/// Text cursor
 25		/// </summary>
 26		private class TextCursor
 27		{
 28			#region Constants
 29			/// <summary>
 30			/// The interval time (in seconds) for change the visible state from the
 31			/// cursor
 32			/// </summary>
 33			private const float BlinkInterval = 0.5f; // 500 ms
 34			#endregion
 35
 36			#region IsVisibleNow (Public)
 37			/// <summary>
 38			/// Returns 'true' if the cursor can/needs to be shown now.
 39			/// </summary>
 40			public bool IsVisibleNow
 41			{
 42				get;
 43				private set;
 44			}
 45			#endregion
 46
 47			#region CharNumber (Public)
 48			/// <summary>
 49			/// Sets the cursor position after specified char (by its number,
 50			/// 1 - ...).
 51			/// </summary>
 52			public int CharNumber
 53			{
 54				get
 55				{
 56					return charNumber;
 57				} // get
 58				set
 59				{
 60					charNumber = value;
 61
 62					string subText = Owner.Text.Substring(0, charNumber);
 63					cursorTextWidth = (subText.Length > 0)
 64					                  	? Owner.TextFont.Measure(subText).Width
 65					                  	: 0.0f;
 66				} // set
 67			}
 68			#endregion
 69
 70			#region Private
 71
 72			#region charNumber (Private)
 73			private int charNumber;
 74			#endregion
 75
 76			#region Owner (Private)
 77			/// <summary>
 78			/// The Text box where the cursor belongs to.
 79			/// </summary>
 80			private readonly TextBox Owner;
 81			#endregion
 82
 83			#region fullTextWidth (Private)
 84			/// <summary>
 85			/// The with of the whole text of the TextBox (required for setting the
 86			/// cursor position correctly for all text alignment modes).
 87			/// </summary>
 88			private float fullTextWidth;
 89			#endregion
 90
 91			#region cursorTextWidth (Private)
 92			/// <summary>
 93			/// The current from the text beginning to know where to draw the final
 94			/// current cursor line
 95			/// </summary>
 96			private float cursorTextWidth;
 97			#endregion
 98
 99			#region currentDrawnTime (Private)
100			/// <summary>
101			/// The duration (in seconds) thenceforth the cursor is shown.
102			/// (-> Used for the "blinking effect")
103			/// </summary>
104			private float currentDrawnTime;
105			#endregion
106
107			#endregion
108
109			#region Constructors
110			/// <summary>
111			/// Create text cursor
112			/// </summary>
113			/// <param name="setOwner">Set owner</param>
114			public TextCursor(TextBox setOwner)
115			{
116				Owner = setOwner;
117			}
118			#endregion
119
120			#region OnOwnerTextHasChanged (Public)
121			/// <summary>
122			/// Occurs every time the amount of text character changes, e.g by
123			/// adding or removing one while the text editing mode is enabled.
124			/// </summary>
125			public void OnOwnerTextHasChanged()
126			{
127				string fullText = Owner.Text;
128				fullTextWidth = (String.IsNullOrEmpty(fullText))
129				                	? 0.0f
130				                	: Owner.TextFont.Measure(fullText).Width;
131
132				// LOOK later:
133				// Currently we only support that the cursor is always located at the
134				// end of the text
135				CharNumber = fullText.Length;
136			}
137			#endregion
138
139			#region Update (Public)
140			/// <summary>
141			/// Does all the magic to handle the cursor events like blinking.
142			/// </summary>
143			public void Update()
144			{
145				if (IsVisibleNow)
146				{
147					// Decrease by the time that the last tick needed
148					currentDrawnTime += Time.Delta;
149					// and check new if we have to disable the cursor drawing now (again)
150					if (currentDrawnTime >= BlinkInterval)
151					{
152						currentDrawnTime = BlinkInterval;
153						IsVisibleNow = false;
154					}
155				}
156				else // IsVisibleNow == false
157				{
158					// Increase by the time that the last tick needed
159					currentDrawnTime -= Time.Delta;
160					// and check new if we can enable the cursor drawing now (again)
161					if (currentDrawnTime <= 0.0f)
162					{
163						currentDrawnTime = 0.0f;
164						IsVisibleNow = true;
165					}
166				}
167			}
168			#endregion
169
170			#region Draw (Public)
171			/// <summary>
172			/// Visualizes the cursor.
173			/// </summary>
174			public void Draw(Color cursorColor)
175			{
176				Rectangle textArea = Owner.TextContentElement.DrawArea;
177
178				// Compute the quad space offset from text start
179				float cursorTextOffset;
180
181				#region Compute the offset based on the current text alignment mode
182				switch (Owner.TextFont.HorizontalAlignment)
183				{
184					case HorizontalAlignment.Left:
185						cursorTextOffset = cursorTextWidth;
186						break;
187
188					case HorizontalAlignment.Centered:
189						cursorTextOffset = (textArea.Width / 2) - (fullTextWidth / 2) +
190						                   cursorTextWidth;
191						break;
192
193					case HorizontalAlignment.Right:
194						cursorTextOffset = textArea.Right - fullTextWidth +
195						                   cursorTextWidth;
196						break;
197
198					default:
199						cursorTextOffset = 0.01f;
200						Log.Warning("The set text alignment mode which is set by the " +
201						            "Font isn't supported by the TextBox yet, so can't show the. " +
202						            "text cursor at the right position.");
203						break;
204				}
205				#endregion
206
207				// inclusive the current max. text height (based on the current font)
208				float halfFontHeight = Owner.TextFont.LineHeight / 2;
209
210				// to specify the top
211				Point topCurorPos = new Point(textArea.Left + cursorTextOffset,
212					textArea.Center.Y - halfFontHeight);
213				// and the bottom point
214				Point bottumCurorPos = new Point(topCurorPos.X,
215					textArea.Center.Y + halfFontHeight);
216
217				// of the cursor line we want to draw
218				Line.Draw(topCurorPos, bottumCurorPos, cursorColor);
219			}
220			#endregion
221
222			#region StartBlinking (Public)
223			/// <summary>
224			/// Starts the blinking of the cursor.
225			/// </summary>
226			public void StartBlinking()
227			{
228				IsVisibleNow = true;
229				currentDrawnTime = 0.0f;
230			}
231			#endregion
232
233			#region StopBlinking (Public)
234			/// <summary>
235			/// Stops the blinking of the cursor.
236			/// </summary>
237			public void StopBlinking()
238			{
239				IsVisibleNow = false;
240				currentDrawnTime = BlinkInterval;
241			}
242			#endregion
243		}
244		#endregion
245
246		#region Constants
247		/// <summary>
248		/// The current version of the implementation of this class.
249		/// </summary>
250		private const int VersionNumber = 1;
251		#endregion
252
253		#region MaxCharacters (Public)
254		/// <summary>
255		/// Maximum characters
256		/// </summary>
257		public int MaxCharacters
258		{
259			get;
260			set;
261		}
262		#endregion
263
264		#region IsReadOnly (Public)
265		/// <summary>
266		/// Is the Text box read only
267		/// </summary>
268		public bool IsReadOnly
269		{
270			get;
271			set;
272		}
273		#endregion
274
275		#region Protected
276
277		#region FallbackDesign (Protected)
278		/// <summary>
279		/// Defines the design which will be used if no "Design" was set
280		/// explicitely.
281		/// </summary>
282		protected override ControlDesign FallbackDesign
283		{
284			get
285			{
286				return Theme.Current.TextboxDesign;
287			} // get
288		}
289		#endregion
290
291		///// <summary>
292		///// 'True' if the user wishes to enable text editing on this text box.
293		///// If the editing will be really enabled depends on the current state of
294		///// the text box.
295		///// </summary>
296		//private bool isTextEditingWished;
297
298		///// <summary>
299		///// Is text editing on
300		///// </summary>
301		//protected bool IsTextEditingOn
302		//{
303		//  get
304		//  {
305		//    return isTextEditingWished &&
306		//      IsReadOnly == false &&
307		//      State >= ElementState.Enabled;
308		//  } // get
309		//  set
310		//  {
311		//    if (isTextEditingWished != value)
312		//    {
313		//      isTextEditingWished = value;
314
315		//      if (isTextEditingWished)
316		//      {
317		//        // Inform the cursor that he can start with blinking now (at the
318		//        // end of the text)
319		//        textCursor.CharNumber = Text.Length;
320		//        textCursor.StartBlinking();
321		//        // and start showing an On-Screen-Keyboard (if no physical keyboard
322		//        // is connected)
323		//        Input.Keyboard.ShowOnScreenKeyboard();
324		//      } // if
325		//      else
326		//      {
327		//        // Inform the cursor that he can stop with blinking now
328		//        textCursor.StopBlinking();
329		//        // and also hide the On-Screen-Keaboard again (if there was one)
330		//        Input.Keyboard.HideOnScreenKeyboard();
331		//      } // else
332		//    } // if
333		//  } // set
334		//}
335
336		#region IsTextEditingOn (Protected)
337		/// <summary>
338		/// Is text editing on
339		/// </summary>
340		protected bool IsTextEditingOn
341		{
342			get
343			{
344				return State == ElementState.Active &&
345				       IsReadOnly == false;
346			} // get
347			set
348			{
349				// If the editing mode is wished
350				if (value &&
351				    // but is still not enabled
352				    State != ElementState.Active)
353				{
354					// then do it now
355					State = ElementState.Active;
356
357					// Inform the cursor that he can start with blinking now (at the
358					// end of the text)
359					textCursor.CharNumber = Text.Length;
360					textCursor.StartBlinking();
361					// and start showing an On-Screen-Keyboard (if no physical keyboard
362					// is connected)
363					Input.Keyboard.ShowOnScreenKeyboard();
364				} // if
365
366					// otherwise the editing mode should be disabled now
367				else if (value == false &&
368				         // and the control is still in there
369				         State == ElementState.Active)
370				{
371					// then deactivate it now
372					State = ElementState.Enabled;
373
374					// Inform the cursor that he can stop with blinking now
375					textCursor.StopBlinking();
376					// and also hide the On-Screen-Keaboard again (if there was one)
377					Input.Keyboard.HideOnScreenKeyboard();
378				} // else
379			} // set
380		}
381		#endregion
382
383		#endregion
384
385		#region Private
386
387		#region textCursor (Private)
388		/// <summary>
389		/// The instance of the text cursor we show if the text editing mode is
390		/// enabled.
391		/// </summary>
392		private readonly TextCursor textCursor;
393		#endregion
394
395		#endregion
396
397		#region Constructors
398		/// <summary>
399		/// Creates a Text box instance.
400		/// </summary>
401		public TextBox()
402		{
403			textCursor = new TextCursor(this);
404		}
405		#endregion
406
407		#region Save (Public)
408		/// <summary>
409		/// Saves all data which are necessary to restore the object again.
410		/// </summary>
411		/// <param name="dataWriter">
412		/// The writer which contains the stream where the data should be saved
413		/// into now.
414		/// </param>
415		public override void Save(BinaryWriter dataWriter)
416		{
417			// At first we write the data of the base class
418			base.Save(dataWriter);
419
420			// and it current working version
421			dataWriter.Write(VersionNumber);
422
423			dataWriter.Write(MaxCharacters);
424			dataWriter.Write(IsReadOnly);
425		}
426		#endregion
427
428		#region Load (Public)
429		/// <summary>
430		/// Loads and restores all previously saved values that belongs to this
431		/// class only from the given data reader.
432		/// </summary>
433		/// <param name="dataReader">
434		/// The reader which contains the stream with the saved data which needs to
435		/// be loaded now.
436		/// </param>
437		public override void Load(BinaryReader dataReader)
438		{
439			// At first we need to load all data of the base class
440			base.Load(dataReader);
441
442			int version = dataReader.ReadInt32();
443			switch (version)
444			{
445				case 1:
446					MaxCharacters = dataReader.ReadInt32();
447					IsReadOnly = dataReader.ReadBoolean();
448					// ...done
449					break;
450
451				default:
452					Log.InvalidVersionWarning(GetType().Name, version, VersionNumber);
453					break;
454			} // switch
455		}
456		#endregion
457
458		#region Methods (Private)
459
460		#region OnInputEvent
461		/// <summary>
462		/// On input event
463		/// </summary>
464		/// <param name="input">Input data</param>
465		protected override void OnInputEvent(CommandTrigger input)
466		{
467			switch (input.CommandName)
468			{
469				case Command.UIClick:
470					// Only allow text editing if the user has clicked inside the control
471					if (IsInControl(input.Position))
472					{
473						// If the text editing mode is already enabled then we assume the
474						// user wants to set the cursor at a specific position
475						if (IsTextEditingOn)
476						{
477							// removing text (by BackSpace/Del) by us or telling it the
478							// char index in the string where to start editing
479							//textCursor.DetermineCharNumber(inputData.Position -
480							//  TextArea.Position);
481						} // if
482
483							// otherwise if the text editing is not enabled yet then do it now
484						else
485						{
486							IsTextEditingOn = true;
487						} // else
488					} // if
489
490						// if the click was outside the text editing mode will be disabled
491						// again (if it was enabled before)
492					else if (IsTextEditingOn &&
493					         input.Button != InputButton.Space)
494					{
495						IsTextEditingOn = false;
496					} // if
497					break;
498
499				default:
500					base.OnInputEvent(input);
501					break;
502			} // switch
503		}
504		#endregion
505
506		#region OnTextChanging
507		/// <summary>
508		/// On text changing
509		/// </summary>
510		/// <param name="oldText">Old text</param>
511		/// <returns>
512		/// 'True' if the new value can be used or 'false' if the change should be
513		/// aborted.
514		/// </returns>
515		protected override bool OnTextChanging(string oldText)
516		{
517			if (base.OnTextChanging(oldText))
518			{
519				if (MaxCharacters > 0)
520				{
521					Text = Text.MaxStringLength(MaxCharacters,
522						StringHelper.CutModes.End);
523				} // if
524
525				// and also inform the text cursor about the new text size to let it
526				// readjust the current cursor position (currently always at the end) 
527				textCursor.OnOwnerTextHasChanged();
528
529				return true;
530			} // if
531
532			return false;
533		}
534		#endregion
535
536		#region UpdateDrawData
537		/// <summary>
538		/// Updates all data of this element which are required for the following
539		/// draw call.
540		/// </summary>
541		/// <remarks>
542		/// This method will only be called if the element is in an enabled state.
543		/// </remarks>
544		protected override void UpdateDrawData()
545		{
546			base.UpdateDrawData();
547
548			if (IsTextEditingOn)
549			{
550				string newText = Text;
551				Input.Keyboard.HandleInput(ref newText);
552				if (newText != Text)
553				{
554					// If we pressed the "Enter" key then just finish the text editing
555					// mode
556					if (newText.EndsWith("\n"))
557					{
558						IsTextEditingOn = false;
559						return;
560					} // if
561
562					Text = newText;
563				} // if
564
565				textCursor.Update();
566			} // if
567		}
568		#endregion
569
570		#region DrawData
571		/// <summary>
572		/// Draws all data of this TextBox which needs to be visualized.
573		/// </summary>
574		/// <remarks>
575		/// This method will only be called if the TextBox is in a visible state.
576		/// </remarks>
577		protected override void DrawData()
578		{
579			base.DrawData();
580
581			// If the text editing mode is enabled
582			if (IsTextEditingOn)
583			{
584				// draw the cursor
585				if (textCursor.IsVisibleNow)
586				{
587					textCursor.Draw(TextFont.Color);
588				} // if
589			} // if
590		}
591		#endregion
592
593		#region DrawDebugInfo
594		/// <summary>
595		/// Draw debug info
596		/// </summary>
597		protected override void DrawDebugInfo()
598		{
599			base.DrawDebugInfo();
600
601			float gap = 0.01f;
602			Point startPos = new Point(DrawArea.Center.X, DrawArea.Bottom);
603			startPos.Y += gap;
604
605			TextFont.Draw("IsTextEditingOn '" + IsTextEditingOn + "'",
606				Rectangle.FromCenter(startPos, new Size(0.5f, TextFont.LineHeight)));
607			startPos.Y += gap;
608		}
609		#endregion
610
611		#endregion
612
613		/// <summary>
614		/// Tests for TextBox controls
615		/// </summary>
616		[Category("Visual")]
617		internal class Tests
618		{
619			#region DisplayTextbox (Static)
620			/// <summary>
621			/// Display text box
622			/// </summary>
623			[Test]
624			public static void DisplayTextbox()
625			{
626				TextBox testTextBox = new TextBox
627				{
628					LocalArea = new Rectangle(0.2f, 0.2f, 0.5f, 0.1f),
629					//Design = new TextControlDesign
630					//{
631					//  //Background = currentTheme.LabelDesign.Background,
632					//  Background = null,
633					//  DisabledBackground = currentTheme.LabelDesign.DisabledBackground,
634					//  TextFont = currentTheme.LabelDesign.TextFont.Clone(Color.Green),
635					//},
636					Text = "Text.........Text",
637					//Text = "T",
638				};
639
640				Screen testScreen = new Screen();
641				testScreen.Add(testTextBox);
642
643				// Open now the scene to "activate" for the test
644				testScreen.Open();
645
646				Application.Start(delegate
647				{
648				});
649			}
650			#endregion
651		}
652	}
653}