TextValidateField.cs 9.3 KB

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