TextValidateField.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. #nullable enable
  2. namespace Terminal.Gui.Views;
  3. /// <summary>Masked text editor that validates input through a <see cref="ITextValidateProvider"/></summary>
  4. public class TextValidateField : View, IDesignable
  5. {
  6. private const int DEFAULT_LENGTH = 10;
  7. private int _cursorPosition;
  8. private ITextValidateProvider? _provider;
  9. /// <summary>
  10. /// Initializes a new instance of the <see cref="TextValidateField"/> class.
  11. /// </summary>
  12. public TextValidateField ()
  13. {
  14. Height = Dim.Auto (minimumContentDim: 1);
  15. CanFocus = true;
  16. // Things this view knows how to do
  17. AddCommand (
  18. Command.LeftStart,
  19. () =>
  20. {
  21. HomeKeyHandler ();
  22. return true;
  23. }
  24. );
  25. AddCommand (
  26. Command.RightEnd,
  27. () =>
  28. {
  29. EndKeyHandler ();
  30. return true;
  31. }
  32. );
  33. AddCommand (
  34. Command.DeleteCharRight,
  35. () =>
  36. {
  37. DeleteKeyHandler ();
  38. return true;
  39. }
  40. );
  41. AddCommand (
  42. Command.DeleteCharLeft,
  43. () =>
  44. {
  45. BackspaceKeyHandler ();
  46. return true;
  47. }
  48. );
  49. AddCommand (
  50. Command.Left,
  51. () =>
  52. {
  53. CursorLeft ();
  54. return true;
  55. }
  56. );
  57. AddCommand (
  58. Command.Right,
  59. () =>
  60. {
  61. CursorRight ();
  62. return true;
  63. }
  64. );
  65. // Default keybindings for this view
  66. KeyBindings.Add (Key.Home, Command.LeftStart);
  67. KeyBindings.Add (Key.End, Command.RightEnd);
  68. KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
  69. KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
  70. KeyBindings.Add (Key.CursorLeft, Command.Left);
  71. KeyBindings.Add (Key.CursorRight, Command.Right);
  72. }
  73. /// <summary>This property returns true if the input is valid.</summary>
  74. public virtual bool IsValid
  75. {
  76. get
  77. {
  78. if (_provider is null)
  79. {
  80. return false;
  81. }
  82. return _provider.IsValid;
  83. }
  84. }
  85. /// <summary>Provider</summary>
  86. public ITextValidateProvider? Provider
  87. {
  88. get => _provider;
  89. set
  90. {
  91. _provider = value;
  92. if (_provider!.Fixed)
  93. {
  94. Width = _provider.DisplayText == string.Empty
  95. ? DEFAULT_LENGTH
  96. : _provider.DisplayText.Length;
  97. }
  98. // HomeKeyHandler already call SetNeedsDisplay
  99. HomeKeyHandler ();
  100. }
  101. }
  102. /// <summary>Text</summary>
  103. public new string Text
  104. {
  105. get
  106. {
  107. if (_provider is null)
  108. {
  109. return string.Empty;
  110. }
  111. return _provider.Text;
  112. }
  113. set
  114. {
  115. if (_provider is null)
  116. {
  117. return;
  118. }
  119. _provider.Text = value;
  120. SetNeedsDraw ();
  121. }
  122. }
  123. /// <inheritdoc/>
  124. protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
  125. {
  126. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
  127. {
  128. int c = _provider!.Cursor (mouseEvent.Position.X - GetMargins (Viewport.Width).left);
  129. if (_provider.Fixed == false && TextAlignment == Alignment.End && Text.Length > 0)
  130. {
  131. c++;
  132. }
  133. _cursorPosition = c;
  134. SetFocus ();
  135. SetNeedsDraw ();
  136. return true;
  137. }
  138. return false;
  139. }
  140. /// <inheritdoc/>
  141. protected override bool OnDrawingContent ()
  142. {
  143. if (_provider is null)
  144. {
  145. Move (0, 0);
  146. AddStr ("Error: ITextValidateProvider not set!");
  147. return true;
  148. }
  149. VisualRole role = HasFocus ? VisualRole.Focus : VisualRole.Editable;
  150. Attribute textColor = IsValid ? GetAttributeForRole (role) : SchemeManager.GetScheme (Schemes.Error).GetAttributeForRole (role);
  151. (int marginLeft, int marginRight) = GetMargins (Viewport.Width);
  152. Move (0, 0);
  153. // Left Margin
  154. SetAttribute (textColor);
  155. for (var i = 0; i < marginLeft; i++)
  156. {
  157. AddRune ((Rune)' ');
  158. }
  159. // Content
  160. SetAttribute (textColor);
  161. // Content
  162. for (var i = 0; i < _provider.DisplayText.Length; i++)
  163. {
  164. AddRune ((Rune)_provider.DisplayText [i]);
  165. }
  166. // Right Margin
  167. SetAttribute (textColor);
  168. for (var i = 0; i < marginRight; i++)
  169. {
  170. AddRune ((Rune)' ');
  171. }
  172. return true;
  173. }
  174. /// <inheritdoc/>
  175. protected override bool OnKeyDownNotHandled (Key key)
  176. {
  177. if (_provider is null)
  178. {
  179. return false;
  180. }
  181. if (key.AsRune == default (Rune) || key == Application.QuitKey)
  182. {
  183. return false;
  184. }
  185. Rune rune = key.AsRune;
  186. bool inserted = _provider.InsertAt ((char)rune.Value, _cursorPosition);
  187. if (inserted)
  188. {
  189. CursorRight ();
  190. return true;
  191. }
  192. return false;
  193. }
  194. /// <inheritdoc/>
  195. public override Point? PositionCursor ()
  196. {
  197. (int left, _) = GetMargins (Viewport.Width);
  198. // Fixed = true, is for inputs that have fixed width, like masked ones.
  199. // Fixed = false, is for normal input.
  200. // When it's right-aligned and it's a normal input, the cursor behaves differently.
  201. int curPos;
  202. if (_provider?.Fixed == false && TextAlignment == Alignment.End)
  203. {
  204. curPos = _cursorPosition + left - 1;
  205. }
  206. else
  207. {
  208. curPos = _cursorPosition + left;
  209. }
  210. Move (curPos, 0);
  211. return new (curPos, 0);
  212. }
  213. /// <summary>Delete char at cursor position - 1, moving the cursor.</summary>
  214. /// <returns></returns>
  215. private bool BackspaceKeyHandler ()
  216. {
  217. if (_provider!.Fixed == false && TextAlignment == Alignment.End && _cursorPosition <= 1)
  218. {
  219. return false;
  220. }
  221. _cursorPosition = _provider.CursorLeft (_cursorPosition);
  222. _provider.Delete (_cursorPosition);
  223. SetNeedsDraw ();
  224. return true;
  225. }
  226. /// <summary>Try to move the cursor to the left.</summary>
  227. /// <returns>True if moved.</returns>
  228. private bool CursorLeft ()
  229. {
  230. if (_provider is null)
  231. {
  232. return false;
  233. }
  234. int current = _cursorPosition;
  235. _cursorPosition = _provider.CursorLeft (_cursorPosition);
  236. SetNeedsDraw ();
  237. return current != _cursorPosition;
  238. }
  239. /// <summary>Try to move the cursor to the right.</summary>
  240. /// <returns>True if moved.</returns>
  241. private bool CursorRight ()
  242. {
  243. if (_provider is null)
  244. {
  245. return false;
  246. }
  247. int current = _cursorPosition;
  248. _cursorPosition = _provider.CursorRight (_cursorPosition);
  249. SetNeedsDraw ();
  250. return current != _cursorPosition;
  251. }
  252. /// <summary>Deletes char at current position.</summary>
  253. /// <returns></returns>
  254. private bool DeleteKeyHandler ()
  255. {
  256. if (_provider!.Fixed == false && TextAlignment == Alignment.End)
  257. {
  258. _cursorPosition = _provider.CursorLeft (_cursorPosition);
  259. }
  260. _provider.Delete (_cursorPosition);
  261. SetNeedsDraw ();
  262. return true;
  263. }
  264. /// <summary>Moves the cursor to the last char.</summary>
  265. /// <returns></returns>
  266. private bool EndKeyHandler ()
  267. {
  268. _cursorPosition = _provider!.CursorEnd ();
  269. SetNeedsDraw ();
  270. return true;
  271. }
  272. /// <summary>Margins for text alignment.</summary>
  273. /// <param name="width">Total width</param>
  274. /// <returns>Left and right margins</returns>
  275. private (int left, int right) GetMargins (int width)
  276. {
  277. int count = Text.Length;
  278. int total = width - count;
  279. return TextAlignment switch
  280. {
  281. Alignment.Start => (0, total),
  282. Alignment.Center => (total / 2, total / 2 + total % 2),
  283. Alignment.End => (total, 0),
  284. _ => (0, total)
  285. };
  286. }
  287. /// <summary>Moves the cursor to first char.</summary>
  288. /// <returns></returns>
  289. private bool HomeKeyHandler ()
  290. {
  291. _cursorPosition = _provider!.CursorStart ();
  292. SetNeedsDraw ();
  293. return true;
  294. }
  295. /// <inheritdoc />
  296. public bool EnableForDesign ()
  297. {
  298. TextRegexProvider provider = new ("^([0-9]?[0-9]?[0-9]|1000)$") { ValidateOnInput = false };
  299. BorderStyle = LineStyle.Single;
  300. Title = provider.Pattern;
  301. Provider = provider;
  302. Text = "999";
  303. return true;
  304. }
  305. }