/Scenes/UserInterfaces/Controls/TextBox.cs
# · C# · 653 lines · 360 code · 72 blank · 221 comment · 28 complexity · 215d8c071557ad2164189df66ac0ab04 MD5 · raw file
- using System;
- using System.IO;
- using Delta.Engine;
- using Delta.InputSystem;
- using Delta.Rendering.Basics.Drawing;
- using Delta.Scenes.Enums;
- using Delta.Utilities;
- using Delta.Utilities.Datatypes;
- using Delta.Utilities.Datatypes.Advanced;
- using Delta.Utilities.Helpers;
- using NUnit.Framework;
-
- namespace Delta.Scenes.UserInterfaces.Controls
- {
- /// <summary>
- /// This class represents a simple (single-line) Text box control which
- /// allows the user doing input like e.g. to enter his name and confirm it by
- /// an the 'Enter' key.
- /// </summary>
- public class TextBox : Label
- {
- #region TextCursor Class
- /// <summary>
- /// Text cursor
- /// </summary>
- private class TextCursor
- {
- #region Constants
- /// <summary>
- /// The interval time (in seconds) for change the visible state from the
- /// cursor
- /// </summary>
- private const float BlinkInterval = 0.5f; // 500 ms
- #endregion
-
- #region IsVisibleNow (Public)
- /// <summary>
- /// Returns 'true' if the cursor can/needs to be shown now.
- /// </summary>
- public bool IsVisibleNow
- {
- get;
- private set;
- }
- #endregion
-
- #region CharNumber (Public)
- /// <summary>
- /// Sets the cursor position after specified char (by its number,
- /// 1 - ...).
- /// </summary>
- public int CharNumber
- {
- get
- {
- return charNumber;
- } // get
- set
- {
- charNumber = value;
-
- string subText = Owner.Text.Substring(0, charNumber);
- cursorTextWidth = (subText.Length > 0)
- ? Owner.TextFont.Measure(subText).Width
- : 0.0f;
- } // set
- }
- #endregion
-
- #region Private
-
- #region charNumber (Private)
- private int charNumber;
- #endregion
-
- #region Owner (Private)
- /// <summary>
- /// The Text box where the cursor belongs to.
- /// </summary>
- private readonly TextBox Owner;
- #endregion
-
- #region fullTextWidth (Private)
- /// <summary>
- /// The with of the whole text of the TextBox (required for setting the
- /// cursor position correctly for all text alignment modes).
- /// </summary>
- private float fullTextWidth;
- #endregion
-
- #region cursorTextWidth (Private)
- /// <summary>
- /// The current from the text beginning to know where to draw the final
- /// current cursor line
- /// </summary>
- private float cursorTextWidth;
- #endregion
-
- #region currentDrawnTime (Private)
- /// <summary>
- /// The duration (in seconds) thenceforth the cursor is shown.
- /// (-> Used for the "blinking effect")
- /// </summary>
- private float currentDrawnTime;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create text cursor
- /// </summary>
- /// <param name="setOwner">Set owner</param>
- public TextCursor(TextBox setOwner)
- {
- Owner = setOwner;
- }
- #endregion
-
- #region OnOwnerTextHasChanged (Public)
- /// <summary>
- /// Occurs every time the amount of text character changes, e.g by
- /// adding or removing one while the text editing mode is enabled.
- /// </summary>
- public void OnOwnerTextHasChanged()
- {
- string fullText = Owner.Text;
- fullTextWidth = (String.IsNullOrEmpty(fullText))
- ? 0.0f
- : Owner.TextFont.Measure(fullText).Width;
-
- // LOOK later:
- // Currently we only support that the cursor is always located at the
- // end of the text
- CharNumber = fullText.Length;
- }
- #endregion
-
- #region Update (Public)
- /// <summary>
- /// Does all the magic to handle the cursor events like blinking.
- /// </summary>
- public void Update()
- {
- if (IsVisibleNow)
- {
- // Decrease by the time that the last tick needed
- currentDrawnTime += Time.Delta;
- // and check new if we have to disable the cursor drawing now (again)
- if (currentDrawnTime >= BlinkInterval)
- {
- currentDrawnTime = BlinkInterval;
- IsVisibleNow = false;
- }
- }
- else // IsVisibleNow == false
- {
- // Increase by the time that the last tick needed
- currentDrawnTime -= Time.Delta;
- // and check new if we can enable the cursor drawing now (again)
- if (currentDrawnTime <= 0.0f)
- {
- currentDrawnTime = 0.0f;
- IsVisibleNow = true;
- }
- }
- }
- #endregion
-
- #region Draw (Public)
- /// <summary>
- /// Visualizes the cursor.
- /// </summary>
- public void Draw(Color cursorColor)
- {
- Rectangle textArea = Owner.TextContentElement.DrawArea;
-
- // Compute the quad space offset from text start
- float cursorTextOffset;
-
- #region Compute the offset based on the current text alignment mode
- switch (Owner.TextFont.HorizontalAlignment)
- {
- case HorizontalAlignment.Left:
- cursorTextOffset = cursorTextWidth;
- break;
-
- case HorizontalAlignment.Centered:
- cursorTextOffset = (textArea.Width / 2) - (fullTextWidth / 2) +
- cursorTextWidth;
- break;
-
- case HorizontalAlignment.Right:
- cursorTextOffset = textArea.Right - fullTextWidth +
- cursorTextWidth;
- break;
-
- default:
- cursorTextOffset = 0.01f;
- Log.Warning("The set text alignment mode which is set by the " +
- "Font isn't supported by the TextBox yet, so can't show the. " +
- "text cursor at the right position.");
- break;
- }
- #endregion
-
- // inclusive the current max. text height (based on the current font)
- float halfFontHeight = Owner.TextFont.LineHeight / 2;
-
- // to specify the top
- Point topCurorPos = new Point(textArea.Left + cursorTextOffset,
- textArea.Center.Y - halfFontHeight);
- // and the bottom point
- Point bottumCurorPos = new Point(topCurorPos.X,
- textArea.Center.Y + halfFontHeight);
-
- // of the cursor line we want to draw
- Line.Draw(topCurorPos, bottumCurorPos, cursorColor);
- }
- #endregion
-
- #region StartBlinking (Public)
- /// <summary>
- /// Starts the blinking of the cursor.
- /// </summary>
- public void StartBlinking()
- {
- IsVisibleNow = true;
- currentDrawnTime = 0.0f;
- }
- #endregion
-
- #region StopBlinking (Public)
- /// <summary>
- /// Stops the blinking of the cursor.
- /// </summary>
- public void StopBlinking()
- {
- IsVisibleNow = false;
- currentDrawnTime = BlinkInterval;
- }
- #endregion
- }
- #endregion
-
- #region Constants
- /// <summary>
- /// The current version of the implementation of this class.
- /// </summary>
- private const int VersionNumber = 1;
- #endregion
-
- #region MaxCharacters (Public)
- /// <summary>
- /// Maximum characters
- /// </summary>
- public int MaxCharacters
- {
- get;
- set;
- }
- #endregion
-
- #region IsReadOnly (Public)
- /// <summary>
- /// Is the Text box read only
- /// </summary>
- public bool IsReadOnly
- {
- get;
- set;
- }
- #endregion
-
- #region Protected
-
- #region FallbackDesign (Protected)
- /// <summary>
- /// Defines the design which will be used if no "Design" was set
- /// explicitely.
- /// </summary>
- protected override ControlDesign FallbackDesign
- {
- get
- {
- return Theme.Current.TextboxDesign;
- } // get
- }
- #endregion
-
- ///// <summary>
- ///// 'True' if the user wishes to enable text editing on this text box.
- ///// If the editing will be really enabled depends on the current state of
- ///// the text box.
- ///// </summary>
- //private bool isTextEditingWished;
-
- ///// <summary>
- ///// Is text editing on
- ///// </summary>
- //protected bool IsTextEditingOn
- //{
- // get
- // {
- // return isTextEditingWished &&
- // IsReadOnly == false &&
- // State >= ElementState.Enabled;
- // } // get
- // set
- // {
- // if (isTextEditingWished != value)
- // {
- // isTextEditingWished = value;
-
- // if (isTextEditingWished)
- // {
- // // Inform the cursor that he can start with blinking now (at the
- // // end of the text)
- // textCursor.CharNumber = Text.Length;
- // textCursor.StartBlinking();
- // // and start showing an On-Screen-Keyboard (if no physical keyboard
- // // is connected)
- // Input.Keyboard.ShowOnScreenKeyboard();
- // } // if
- // else
- // {
- // // Inform the cursor that he can stop with blinking now
- // textCursor.StopBlinking();
- // // and also hide the On-Screen-Keaboard again (if there was one)
- // Input.Keyboard.HideOnScreenKeyboard();
- // } // else
- // } // if
- // } // set
- //}
-
- #region IsTextEditingOn (Protected)
- /// <summary>
- /// Is text editing on
- /// </summary>
- protected bool IsTextEditingOn
- {
- get
- {
- return State == ElementState.Active &&
- IsReadOnly == false;
- } // get
- set
- {
- // If the editing mode is wished
- if (value &&
- // but is still not enabled
- State != ElementState.Active)
- {
- // then do it now
- State = ElementState.Active;
-
- // Inform the cursor that he can start with blinking now (at the
- // end of the text)
- textCursor.CharNumber = Text.Length;
- textCursor.StartBlinking();
- // and start showing an On-Screen-Keyboard (if no physical keyboard
- // is connected)
- Input.Keyboard.ShowOnScreenKeyboard();
- } // if
-
- // otherwise the editing mode should be disabled now
- else if (value == false &&
- // and the control is still in there
- State == ElementState.Active)
- {
- // then deactivate it now
- State = ElementState.Enabled;
-
- // Inform the cursor that he can stop with blinking now
- textCursor.StopBlinking();
- // and also hide the On-Screen-Keaboard again (if there was one)
- Input.Keyboard.HideOnScreenKeyboard();
- } // else
- } // set
- }
- #endregion
-
- #endregion
-
- #region Private
-
- #region textCursor (Private)
- /// <summary>
- /// The instance of the text cursor we show if the text editing mode is
- /// enabled.
- /// </summary>
- private readonly TextCursor textCursor;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Creates a Text box instance.
- /// </summary>
- public TextBox()
- {
- textCursor = new TextCursor(this);
- }
- #endregion
-
- #region Save (Public)
- /// <summary>
- /// Saves all data which are necessary to restore the object again.
- /// </summary>
- /// <param name="dataWriter">
- /// The writer which contains the stream where the data should be saved
- /// into now.
- /// </param>
- public override void Save(BinaryWriter dataWriter)
- {
- // At first we write the data of the base class
- base.Save(dataWriter);
-
- // and it current working version
- dataWriter.Write(VersionNumber);
-
- dataWriter.Write(MaxCharacters);
- dataWriter.Write(IsReadOnly);
- }
- #endregion
-
- #region Load (Public)
- /// <summary>
- /// Loads and restores all previously saved values that belongs to this
- /// class only from the given data reader.
- /// </summary>
- /// <param name="dataReader">
- /// The reader which contains the stream with the saved data which needs to
- /// be loaded now.
- /// </param>
- public override void Load(BinaryReader dataReader)
- {
- // At first we need to load all data of the base class
- base.Load(dataReader);
-
- int version = dataReader.ReadInt32();
- switch (version)
- {
- case 1:
- MaxCharacters = dataReader.ReadInt32();
- IsReadOnly = dataReader.ReadBoolean();
- // ...done
- break;
-
- default:
- Log.InvalidVersionWarning(GetType().Name, version, VersionNumber);
- break;
- } // switch
- }
- #endregion
-
- #region Methods (Private)
-
- #region OnInputEvent
- /// <summary>
- /// On input event
- /// </summary>
- /// <param name="input">Input data</param>
- protected override void OnInputEvent(CommandTrigger input)
- {
- switch (input.CommandName)
- {
- case Command.UIClick:
- // Only allow text editing if the user has clicked inside the control
- if (IsInControl(input.Position))
- {
- // If the text editing mode is already enabled then we assume the
- // user wants to set the cursor at a specific position
- if (IsTextEditingOn)
- {
- // removing text (by BackSpace/Del) by us or telling it the
- // char index in the string where to start editing
- //textCursor.DetermineCharNumber(inputData.Position -
- // TextArea.Position);
- } // if
-
- // otherwise if the text editing is not enabled yet then do it now
- else
- {
- IsTextEditingOn = true;
- } // else
- } // if
-
- // if the click was outside the text editing mode will be disabled
- // again (if it was enabled before)
- else if (IsTextEditingOn &&
- input.Button != InputButton.Space)
- {
- IsTextEditingOn = false;
- } // if
- break;
-
- default:
- base.OnInputEvent(input);
- break;
- } // switch
- }
- #endregion
-
- #region OnTextChanging
- /// <summary>
- /// On text changing
- /// </summary>
- /// <param name="oldText">Old text</param>
- /// <returns>
- /// 'True' if the new value can be used or 'false' if the change should be
- /// aborted.
- /// </returns>
- protected override bool OnTextChanging(string oldText)
- {
- if (base.OnTextChanging(oldText))
- {
- if (MaxCharacters > 0)
- {
- Text = Text.MaxStringLength(MaxCharacters,
- StringHelper.CutModes.End);
- } // if
-
- // and also inform the text cursor about the new text size to let it
- // readjust the current cursor position (currently always at the end)
- textCursor.OnOwnerTextHasChanged();
-
- return true;
- } // if
-
- return false;
- }
- #endregion
-
- #region UpdateDrawData
- /// <summary>
- /// Updates all data of this element which are required for the following
- /// draw call.
- /// </summary>
- /// <remarks>
- /// This method will only be called if the element is in an enabled state.
- /// </remarks>
- protected override void UpdateDrawData()
- {
- base.UpdateDrawData();
-
- if (IsTextEditingOn)
- {
- string newText = Text;
- Input.Keyboard.HandleInput(ref newText);
- if (newText != Text)
- {
- // If we pressed the "Enter" key then just finish the text editing
- // mode
- if (newText.EndsWith("\n"))
- {
- IsTextEditingOn = false;
- return;
- } // if
-
- Text = newText;
- } // if
-
- textCursor.Update();
- } // if
- }
- #endregion
-
- #region DrawData
- /// <summary>
- /// Draws all data of this TextBox which needs to be visualized.
- /// </summary>
- /// <remarks>
- /// This method will only be called if the TextBox is in a visible state.
- /// </remarks>
- protected override void DrawData()
- {
- base.DrawData();
-
- // If the text editing mode is enabled
- if (IsTextEditingOn)
- {
- // draw the cursor
- if (textCursor.IsVisibleNow)
- {
- textCursor.Draw(TextFont.Color);
- } // if
- } // if
- }
- #endregion
-
- #region DrawDebugInfo
- /// <summary>
- /// Draw debug info
- /// </summary>
- protected override void DrawDebugInfo()
- {
- base.DrawDebugInfo();
-
- float gap = 0.01f;
- Point startPos = new Point(DrawArea.Center.X, DrawArea.Bottom);
- startPos.Y += gap;
-
- TextFont.Draw("IsTextEditingOn '" + IsTextEditingOn + "'",
- Rectangle.FromCenter(startPos, new Size(0.5f, TextFont.LineHeight)));
- startPos.Y += gap;
- }
- #endregion
-
- #endregion
-
- /// <summary>
- /// Tests for TextBox controls
- /// </summary>
- [Category("Visual")]
- internal class Tests
- {
- #region DisplayTextbox (Static)
- /// <summary>
- /// Display text box
- /// </summary>
- [Test]
- public static void DisplayTextbox()
- {
- TextBox testTextBox = new TextBox
- {
- LocalArea = new Rectangle(0.2f, 0.2f, 0.5f, 0.1f),
- //Design = new TextControlDesign
- //{
- // //Background = currentTheme.LabelDesign.Background,
- // Background = null,
- // DisabledBackground = currentTheme.LabelDesign.DisabledBackground,
- // TextFont = currentTheme.LabelDesign.TextFont.Clone(Color.Green),
- //},
- Text = "Text.........Text",
- //Text = "T",
- };
-
- Screen testScreen = new Screen();
- testScreen.Add(testTextBox);
-
- // Open now the scene to "activate" for the test
- testScreen.Open();
-
- Application.Start(delegate
- {
- });
- }
- #endregion
- }
- }
- }