TextPresenter.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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 System;
  4. using System.Linq;
  5. using System.Reactive.Linq;
  6. using Perspex.Media;
  7. using Perspex.Threading;
  8. using Perspex.VisualTree;
  9. namespace Perspex.Controls.Presenters
  10. {
  11. public class TextPresenter : TextBlock
  12. {
  13. public static readonly PerspexProperty<int> CaretIndexProperty =
  14. TextBox.CaretIndexProperty.AddOwner<TextPresenter>();
  15. public static readonly PerspexProperty<int> SelectionStartProperty =
  16. TextBox.SelectionStartProperty.AddOwner<TextPresenter>();
  17. public static readonly PerspexProperty<int> SelectionEndProperty =
  18. TextBox.SelectionEndProperty.AddOwner<TextPresenter>();
  19. private readonly DispatcherTimer _caretTimer;
  20. private bool _caretBlink;
  21. private IObservable<bool> _canScrollHorizontally;
  22. public TextPresenter()
  23. {
  24. _caretTimer = new DispatcherTimer();
  25. _caretTimer.Interval = TimeSpan.FromMilliseconds(500);
  26. _caretTimer.Tick += CaretTimerTick;
  27. _canScrollHorizontally = this.GetObservable(TextWrappingProperty)
  28. .Select(x => x == TextWrapping.NoWrap);
  29. Observable.Merge(
  30. this.GetObservable(SelectionStartProperty),
  31. this.GetObservable(SelectionEndProperty))
  32. .Subscribe(_ => InvalidateFormattedText());
  33. this.GetObservable(CaretIndexProperty)
  34. .Subscribe(CaretIndexChanged);
  35. }
  36. public int CaretIndex
  37. {
  38. get { return GetValue(CaretIndexProperty); }
  39. set { SetValue(CaretIndexProperty, value); }
  40. }
  41. public int SelectionStart
  42. {
  43. get { return GetValue(SelectionStartProperty); }
  44. set { SetValue(SelectionStartProperty, value); }
  45. }
  46. public int SelectionEnd
  47. {
  48. get { return GetValue(SelectionEndProperty); }
  49. set { SetValue(SelectionEndProperty, value); }
  50. }
  51. public int GetCaretIndex(Point point)
  52. {
  53. var hit = FormattedText.HitTestPoint(point);
  54. return hit.TextPosition + (hit.IsTrailing ? 1 : 0);
  55. }
  56. public override void Render(DrawingContext context)
  57. {
  58. var selectionStart = SelectionStart;
  59. var selectionEnd = SelectionEnd;
  60. if (selectionStart != selectionEnd)
  61. {
  62. var start = Math.Min(selectionStart, selectionEnd);
  63. var length = Math.Max(selectionStart, selectionEnd) - start;
  64. var rects = FormattedText.HitTestTextRange(start, length);
  65. var brush = new SolidColorBrush(0xff086f9e);
  66. foreach (var rect in rects)
  67. {
  68. context.FillRectangle(brush, rect);
  69. }
  70. }
  71. base.Render(context);
  72. if (selectionStart == selectionEnd)
  73. {
  74. var backgroundColor = (((Control)TemplatedParent).GetValue(BackgroundProperty) as SolidColorBrush)?.Color;
  75. var caretBrush = Brushes.Black;
  76. if(backgroundColor.HasValue)
  77. {
  78. byte red = (byte)~(backgroundColor.Value.R);
  79. byte green = (byte)~(backgroundColor.Value.G);
  80. byte blue = (byte)~(backgroundColor.Value.B);
  81. caretBrush = new SolidColorBrush(Color.FromRgb(red, green, blue));
  82. }
  83. if (_caretBlink)
  84. {
  85. var charPos = FormattedText.HitTestTextPosition(CaretIndex);
  86. var x = Math.Floor(charPos.X) + 0.5;
  87. var y = Math.Floor(charPos.Y) + 0.5;
  88. var b = Math.Ceiling(charPos.Bottom) - 0.5;
  89. context.DrawLine(
  90. new Pen(caretBrush, 1),
  91. new Point(x, y),
  92. new Point(x, b));
  93. }
  94. }
  95. }
  96. public void ShowCaret()
  97. {
  98. _caretBlink = true;
  99. _caretTimer.Start();
  100. InvalidateVisual();
  101. }
  102. public void HideCaret()
  103. {
  104. _caretBlink = false;
  105. _caretTimer.Stop();
  106. InvalidateVisual();
  107. }
  108. internal void CaretIndexChanged(int caretIndex)
  109. {
  110. if (this.GetVisualParent() != null)
  111. {
  112. _caretBlink = true;
  113. _caretTimer.Stop();
  114. _caretTimer.Start();
  115. InvalidateVisual();
  116. if (IsMeasureValid)
  117. {
  118. var rect = FormattedText.HitTestTextPosition(caretIndex);
  119. this.BringIntoView(rect);
  120. }
  121. else
  122. {
  123. // The measure is currently invalid so there's no point trying to bring the
  124. // current char into view until a measure has been carried out as the scroll
  125. // viewer extents may not be up-to-date.
  126. Dispatcher.UIThread.InvokeAsync(
  127. () =>
  128. {
  129. var rect = FormattedText.HitTestTextPosition(caretIndex);
  130. this.BringIntoView(rect);
  131. },
  132. DispatcherPriority.Normal);
  133. }
  134. }
  135. }
  136. protected override FormattedText CreateFormattedText(Size constraint)
  137. {
  138. var result = base.CreateFormattedText(constraint);
  139. var selectionStart = SelectionStart;
  140. var selectionEnd = SelectionEnd;
  141. var start = Math.Min(selectionStart, selectionEnd);
  142. var length = Math.Max(selectionStart, selectionEnd) - start;
  143. if (length > 0)
  144. {
  145. result.SetForegroundBrush(Brushes.White, start, length);
  146. }
  147. return result;
  148. }
  149. protected override Size MeasureOverride(Size availableSize)
  150. {
  151. var text = Text;
  152. if (!string.IsNullOrWhiteSpace(text))
  153. {
  154. return base.MeasureOverride(availableSize);
  155. }
  156. else
  157. {
  158. // TODO: Pretty sure that measuring "X" isn't the right way to do this...
  159. using (var formattedText = new FormattedText(
  160. "X",
  161. FontFamily,
  162. FontSize,
  163. FontStyle,
  164. TextAlignment,
  165. FontWeight))
  166. {
  167. return formattedText.Measure();
  168. }
  169. }
  170. }
  171. private void CaretTimerTick(object sender, EventArgs e)
  172. {
  173. _caretBlink = !_caretBlink;
  174. InvalidateVisual();
  175. }
  176. }
  177. }