TextBox.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. // Copyright (c) The Perspex Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using Perspex.Input.Platform;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Reactive.Linq;
  8. using Perspex.Controls.Presenters;
  9. using Perspex.Controls.Primitives;
  10. using Perspex.Controls.Templates;
  11. using Perspex.Controls.Utils;
  12. using Perspex.Input;
  13. using Perspex.Interactivity;
  14. using Perspex.Media;
  15. using Perspex.Metadata;
  16. namespace Perspex.Controls
  17. {
  18. public class TextBox : TemplatedControl
  19. {
  20. public static readonly PerspexProperty<bool> AcceptsReturnProperty =
  21. PerspexProperty.Register<TextBox, bool>("AcceptsReturn");
  22. public static readonly PerspexProperty<bool> AcceptsTabProperty =
  23. PerspexProperty.Register<TextBox, bool>("AcceptsTab");
  24. public static readonly PerspexProperty<int> CaretIndexProperty =
  25. PerspexProperty.Register<TextBox, int>("CaretIndex", validate: ValidateCaretIndex);
  26. public static readonly PerspexProperty<int> SelectionStartProperty =
  27. PerspexProperty.Register<TextBox, int>("SelectionStart", validate: ValidateCaretIndex);
  28. public static readonly PerspexProperty<int> SelectionEndProperty =
  29. PerspexProperty.Register<TextBox, int>("SelectionEnd", validate: ValidateCaretIndex);
  30. public static readonly PerspexProperty<string> TextProperty =
  31. TextBlock.TextProperty.AddOwner<TextBox>();
  32. public static readonly PerspexProperty<TextAlignment> TextAlignmentProperty =
  33. TextBlock.TextAlignmentProperty.AddOwner<TextBox>();
  34. public static readonly PerspexProperty<TextWrapping> TextWrappingProperty =
  35. TextBlock.TextWrappingProperty.AddOwner<TextBox>();
  36. public static readonly PerspexProperty<string> WatermarkProperty =
  37. PerspexProperty.Register<TextBox, string>("Watermark");
  38. public static readonly PerspexProperty<bool> UseFloatingWatermarkProperty =
  39. PerspexProperty.Register<TextBox, bool>("UseFloatingWatermark");
  40. private TextPresenter _presenter;
  41. static TextBox()
  42. {
  43. FocusableProperty.OverrideDefaultValue(typeof(TextBox), true);
  44. }
  45. public TextBox()
  46. {
  47. var canScrollHorizontally = GetObservable(AcceptsReturnProperty)
  48. .Select(x => !x);
  49. Bind(
  50. ScrollViewer.CanScrollHorizontallyProperty,
  51. canScrollHorizontally,
  52. BindingPriority.Style);
  53. var horizontalScrollBarVisibility = GetObservable(AcceptsReturnProperty)
  54. .Select(x => x ? ScrollBarVisibility.Auto : ScrollBarVisibility.Hidden);
  55. Bind(
  56. ScrollViewer.HorizontalScrollBarVisibilityProperty,
  57. horizontalScrollBarVisibility,
  58. BindingPriority.Style);
  59. }
  60. public bool AcceptsReturn
  61. {
  62. get { return GetValue(AcceptsReturnProperty); }
  63. set { SetValue(AcceptsReturnProperty, value); }
  64. }
  65. public bool AcceptsTab
  66. {
  67. get { return GetValue(AcceptsTabProperty); }
  68. set { SetValue(AcceptsTabProperty, value); }
  69. }
  70. public int CaretIndex
  71. {
  72. get { return GetValue(CaretIndexProperty); }
  73. set { SetValue(CaretIndexProperty, value); }
  74. }
  75. public int SelectionStart
  76. {
  77. get { return GetValue(SelectionStartProperty); }
  78. set { SetValue(SelectionStartProperty, value); }
  79. }
  80. public int SelectionEnd
  81. {
  82. get { return GetValue(SelectionEndProperty); }
  83. set { SetValue(SelectionEndProperty, value); }
  84. }
  85. [Content]
  86. public string Text
  87. {
  88. get { return GetValue(TextProperty); }
  89. set { SetValue(TextProperty, value); }
  90. }
  91. public TextAlignment TextAlignment
  92. {
  93. get { return GetValue(TextAlignmentProperty); }
  94. set { SetValue(TextAlignmentProperty, value); }
  95. }
  96. public string Watermark
  97. {
  98. get { return GetValue(WatermarkProperty); }
  99. set { SetValue(WatermarkProperty, value); }
  100. }
  101. public bool UseFloatingWatermark
  102. {
  103. get { return GetValue(UseFloatingWatermarkProperty); }
  104. set { SetValue(UseFloatingWatermarkProperty, value); }
  105. }
  106. public TextWrapping TextWrapping
  107. {
  108. get { return GetValue(TextWrappingProperty); }
  109. set { SetValue(TextWrappingProperty, value); }
  110. }
  111. protected override void OnTemplateApplied(INameScope nameScope)
  112. {
  113. _presenter = nameScope.Get<TextPresenter>("PART_TextPresenter");
  114. _presenter.Cursor = new Cursor(StandardCursorType.Ibeam);
  115. }
  116. protected override void OnGotFocus(GotFocusEventArgs e)
  117. {
  118. base.OnGotFocus(e);
  119. _presenter.ShowCaret();
  120. }
  121. protected override void OnLostFocus(RoutedEventArgs e)
  122. {
  123. base.OnLostFocus(e);
  124. SelectionStart = 0;
  125. SelectionEnd = 0;
  126. _presenter.HideCaret();
  127. }
  128. protected override void OnTextInput(TextInputEventArgs e)
  129. {
  130. HandleTextInput(e.Text);
  131. }
  132. private void HandleTextInput(string input)
  133. {
  134. string text = Text ?? string.Empty;
  135. int caretIndex = CaretIndex;
  136. if (!string.IsNullOrEmpty(input))
  137. {
  138. DeleteSelection();
  139. caretIndex = CaretIndex;
  140. text = Text ?? string.Empty;
  141. Text = text.Substring(0, caretIndex) + input + text.Substring(caretIndex);
  142. CaretIndex += input.Length;
  143. SelectionStart = SelectionEnd = CaretIndex;
  144. }
  145. }
  146. private async void Copy()
  147. {
  148. await ((IClipboard)PerspexLocator.Current.GetService(typeof(IClipboard)))
  149. .SetTextAsync(GetSelection());
  150. }
  151. private async void Paste()
  152. {
  153. var text = await ((IClipboard)PerspexLocator.Current.GetService(typeof(IClipboard))).GetTextAsync();
  154. if (text == null)
  155. {
  156. return;
  157. }
  158. HandleTextInput(text);
  159. }
  160. protected override void OnKeyDown(KeyEventArgs e)
  161. {
  162. string text = Text ?? string.Empty;
  163. int caretIndex = CaretIndex;
  164. bool movement = false;
  165. bool handled = true;
  166. var modifiers = e.Modifiers;
  167. switch (e.Key)
  168. {
  169. case Key.A:
  170. if (modifiers == InputModifiers.Control)
  171. {
  172. SelectAll();
  173. }
  174. break;
  175. case Key.C:
  176. if (modifiers == InputModifiers.Control)
  177. {
  178. Copy();
  179. }
  180. break;
  181. case Key.V:
  182. if (modifiers == InputModifiers.Control)
  183. {
  184. Paste();
  185. }
  186. break;
  187. case Key.Left:
  188. MoveHorizontal(-1, modifiers);
  189. movement = true;
  190. break;
  191. case Key.Right:
  192. MoveHorizontal(1, modifiers);
  193. movement = true;
  194. break;
  195. case Key.Up:
  196. MoveVertical(-1, modifiers);
  197. movement = true;
  198. break;
  199. case Key.Down:
  200. MoveVertical(1, modifiers);
  201. movement = true;
  202. break;
  203. case Key.Home:
  204. MoveHome(modifiers);
  205. movement = true;
  206. break;
  207. case Key.End:
  208. MoveEnd(modifiers);
  209. movement = true;
  210. break;
  211. case Key.Back:
  212. if (!DeleteSelection() && CaretIndex > 0)
  213. {
  214. Text = text.Substring(0, caretIndex - 1) + text.Substring(caretIndex);
  215. --CaretIndex;
  216. }
  217. break;
  218. case Key.Delete:
  219. if (!DeleteSelection() && caretIndex < text.Length)
  220. {
  221. Text = text.Substring(0, caretIndex) + text.Substring(caretIndex + 1);
  222. }
  223. break;
  224. case Key.Enter:
  225. if (AcceptsReturn)
  226. {
  227. HandleTextInput("\r\n");
  228. }
  229. break;
  230. case Key.Tab:
  231. if (AcceptsTab)
  232. {
  233. HandleTextInput("\t");
  234. }
  235. else
  236. {
  237. base.OnKeyDown(e);
  238. handled = false;
  239. }
  240. break;
  241. }
  242. if (movement && ((modifiers & InputModifiers.Shift) != 0))
  243. {
  244. SelectionEnd = CaretIndex;
  245. }
  246. else if (movement)
  247. {
  248. SelectionStart = SelectionEnd = CaretIndex;
  249. }
  250. if (handled)
  251. {
  252. e.Handled = true;
  253. }
  254. }
  255. protected override void OnPointerPressed(PointerPressEventArgs e)
  256. {
  257. if (e.Source == _presenter)
  258. {
  259. var point = e.GetPosition(_presenter);
  260. var index = CaretIndex = _presenter.GetCaretIndex(point);
  261. var text = Text;
  262. switch (e.ClickCount)
  263. {
  264. case 1:
  265. SelectionStart = SelectionEnd = index;
  266. break;
  267. case 2:
  268. if (!StringUtils.IsStartOfWord(text, index))
  269. {
  270. SelectionStart = StringUtils.PreviousWord(text, index, false);
  271. }
  272. SelectionEnd = StringUtils.NextWord(text, index, false);
  273. break;
  274. case 3:
  275. SelectionStart = 0;
  276. SelectionEnd = text.Length;
  277. break;
  278. }
  279. e.Device.Capture(_presenter);
  280. e.Handled = true;
  281. }
  282. }
  283. protected override void OnPointerMoved(PointerEventArgs e)
  284. {
  285. if (_presenter != null && e.Device.Captured == _presenter)
  286. {
  287. var point = e.GetPosition(_presenter);
  288. CaretIndex = SelectionEnd = _presenter.GetCaretIndex(point);
  289. }
  290. }
  291. protected override void OnPointerReleased(PointerEventArgs e)
  292. {
  293. if (_presenter != null && e.Device.Captured == _presenter)
  294. {
  295. e.Device.Capture(null);
  296. }
  297. }
  298. private static int ValidateCaretIndex(PerspexObject o, int value)
  299. {
  300. var text = o.GetValue(TextProperty);
  301. var length = (text != null) ? text.Length : 0;
  302. return Math.Max(0, Math.Min(length, value));
  303. }
  304. private void MoveHorizontal(int count, InputModifiers modifiers)
  305. {
  306. var text = Text ?? string.Empty;
  307. var caretIndex = CaretIndex;
  308. if ((modifiers & InputModifiers.Control) != 0)
  309. {
  310. if (count > 0)
  311. {
  312. count = StringUtils.NextWord(text, caretIndex, false) - caretIndex;
  313. }
  314. else
  315. {
  316. count = StringUtils.PreviousWord(text, caretIndex, false) - caretIndex;
  317. }
  318. }
  319. CaretIndex = caretIndex += count;
  320. }
  321. private void MoveVertical(int count, InputModifiers modifiers)
  322. {
  323. var formattedText = _presenter.FormattedText;
  324. var lines = formattedText.GetLines().ToList();
  325. var caretIndex = CaretIndex;
  326. var lineIndex = GetLine(caretIndex, lines) + count;
  327. if (lineIndex >= 0 && lineIndex < lines.Count)
  328. {
  329. var line = lines[lineIndex];
  330. var rect = formattedText.HitTestTextPosition(caretIndex);
  331. var y = count < 0 ? rect.Y : rect.Bottom;
  332. var point = new Point(rect.X, y + (count * (line.Height / 2)));
  333. var hit = formattedText.HitTestPoint(point);
  334. CaretIndex = hit.TextPosition + (hit.IsTrailing ? 1 : 0);
  335. }
  336. }
  337. private void MoveHome(InputModifiers modifiers)
  338. {
  339. var text = Text ?? string.Empty;
  340. var caretIndex = CaretIndex;
  341. if ((modifiers & InputModifiers.Control) != 0)
  342. {
  343. caretIndex = 0;
  344. }
  345. else
  346. {
  347. var lines = _presenter.FormattedText.GetLines();
  348. var pos = 0;
  349. foreach (var line in lines)
  350. {
  351. if (pos + line.Length > caretIndex || pos + line.Length == text.Length)
  352. {
  353. break;
  354. }
  355. pos += line.Length;
  356. }
  357. caretIndex = pos;
  358. }
  359. CaretIndex = caretIndex;
  360. }
  361. private void MoveEnd(InputModifiers modifiers)
  362. {
  363. var text = Text ?? string.Empty;
  364. var caretIndex = CaretIndex;
  365. if ((modifiers & InputModifiers.Control) != 0)
  366. {
  367. caretIndex = text.Length;
  368. }
  369. else
  370. {
  371. var lines = _presenter.FormattedText.GetLines();
  372. var pos = 0;
  373. foreach (var line in lines)
  374. {
  375. pos += line.Length;
  376. if (pos > caretIndex)
  377. {
  378. if (pos < text.Length)
  379. {
  380. --pos;
  381. }
  382. break;
  383. }
  384. }
  385. caretIndex = pos;
  386. }
  387. CaretIndex = caretIndex;
  388. }
  389. private void SelectAll()
  390. {
  391. SelectionStart = 0;
  392. SelectionEnd = Text.Length;
  393. }
  394. private bool DeleteSelection()
  395. {
  396. var selectionStart = SelectionStart;
  397. var selectionEnd = SelectionEnd;
  398. if (selectionStart != selectionEnd)
  399. {
  400. var start = Math.Min(selectionStart, selectionEnd);
  401. var end = Math.Max(selectionStart, selectionEnd);
  402. var text = Text;
  403. Text = text.Substring(0, start) + text.Substring(end);
  404. SelectionStart = SelectionEnd = CaretIndex = start;
  405. return true;
  406. }
  407. else
  408. {
  409. return false;
  410. }
  411. }
  412. private string GetSelection()
  413. {
  414. var selectionStart = SelectionStart;
  415. var selectionEnd = SelectionEnd;
  416. var start = Math.Min(selectionStart, selectionEnd);
  417. var end = Math.Max(selectionStart, selectionEnd);
  418. if (start == end || (Text?.Length ?? 0) < end)
  419. {
  420. return "";
  421. }
  422. return Text.Substring(start, end - start);
  423. }
  424. private int GetLine(int caretIndex, IList<FormattedTextLine> lines)
  425. {
  426. int pos = 0;
  427. int i;
  428. for (i = 0; i < lines.Count; ++i)
  429. {
  430. var line = lines[i];
  431. pos += line.Length;
  432. if (pos > caretIndex)
  433. {
  434. break;
  435. }
  436. }
  437. return i;
  438. }
  439. }
  440. }