PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/Scenes/UserInterfaces/Controls/TextBox.cs

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