ViewText.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. using System.Text;
  2. using System;
  3. using System.Collections.Generic;
  4. namespace Terminal.Gui {
  5. public partial class View {
  6. string _text;
  7. /// <summary>
  8. /// The text displayed by the <see cref="View"/>.
  9. /// </summary>
  10. /// <remarks>
  11. /// <para>
  12. /// The text will be drawn before any subviews are drawn.
  13. /// </para>
  14. /// <para>
  15. /// The text will be drawn starting at the view origin (0, 0) and will be formatted according
  16. /// to <see cref="TextAlignment"/> and <see cref="TextDirection"/>.
  17. /// </para>
  18. /// <para>
  19. /// The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Bounds"/>'s height
  20. /// is 1, the text will be clipped.
  21. /// </para>
  22. /// <para>
  23. /// Set the <see cref="HotKeySpecifier"/> to enable hotkey support. To disable hotkey support set <see cref="HotKeySpecifier"/> to
  24. /// <c>(Rune)0xffff</c>.
  25. /// </para>
  26. /// </remarks>
  27. public virtual string Text {
  28. get => _text;
  29. set {
  30. _text = value;
  31. SetHotKey ();
  32. UpdateTextFormatterText ();
  33. //TextFormatter.Format ();
  34. OnResizeNeeded ();
  35. #if DEBUG
  36. if (_text != null && string.IsNullOrEmpty (Id)) {
  37. Id = _text;
  38. }
  39. #endif
  40. }
  41. }
  42. /// <summary>
  43. /// Gets or sets the <see cref="Gui.TextFormatter"/> used to format <see cref="Text"/>.
  44. /// </summary>
  45. public TextFormatter TextFormatter { get; set; }
  46. /// <summary>
  47. /// Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
  48. /// different format than the default.
  49. /// </summary>
  50. protected virtual void UpdateTextFormatterText ()
  51. {
  52. if (TextFormatter != null) {
  53. TextFormatter.Text = _text;
  54. }
  55. }
  56. /// <summary>
  57. /// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
  58. /// or not when <see cref="TextFormatter.WordWrap"/> is enabled.
  59. /// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when
  60. /// <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
  61. /// </summary>
  62. public virtual bool PreserveTrailingSpaces {
  63. get => TextFormatter.PreserveTrailingSpaces;
  64. set {
  65. if (TextFormatter.PreserveTrailingSpaces != value) {
  66. TextFormatter.PreserveTrailingSpaces = value;
  67. TextFormatter.NeedsFormat = true;
  68. }
  69. }
  70. }
  71. /// <summary>
  72. /// Gets or sets how the View's <see cref="Text"/> is aligned horizontally when drawn. Changing this property will redisplay the <see cref="View"/>.
  73. /// </summary>
  74. /// <value>The text alignment.</value>
  75. public virtual TextAlignment TextAlignment {
  76. get => TextFormatter.Alignment;
  77. set {
  78. TextFormatter.Alignment = value;
  79. UpdateTextFormatterText ();
  80. OnResizeNeeded ();
  81. }
  82. }
  83. /// <summary>
  84. /// Gets or sets how the View's <see cref="Text"/> is aligned vertically when drawn. Changing this property will redisplay the <see cref="View"/>.
  85. /// </summary>
  86. /// <value>The text alignment.</value>
  87. public virtual VerticalTextAlignment VerticalTextAlignment {
  88. get => TextFormatter.VerticalAlignment;
  89. set {
  90. TextFormatter.VerticalAlignment = value;
  91. SetNeedsDisplay ();
  92. }
  93. }
  94. /// <summary>
  95. /// Gets or sets the direction of the View's <see cref="Text"/>. Changing this property will redisplay the <see cref="View"/>.
  96. /// </summary>
  97. /// <value>The text alignment.</value>
  98. public virtual TextDirection TextDirection {
  99. get => TextFormatter.Direction;
  100. set {
  101. UpdateTextDirection (value);
  102. TextFormatter.Direction = value;
  103. }
  104. }
  105. private void UpdateTextDirection (TextDirection newDirection)
  106. {
  107. var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction)
  108. != TextFormatter.IsHorizontalDirection (newDirection);
  109. TextFormatter.Direction = newDirection;
  110. var isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _);
  111. UpdateTextFormatterText ();
  112. if ((!ValidatePosDim && directionChanged && AutoSize)
  113. || (ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize)) {
  114. OnResizeNeeded ();
  115. } else if (directionChanged && IsAdded) {
  116. ResizeBoundsToFit (Bounds.Size);
  117. // BUGBUG: I think this call is redundant.
  118. SetFrameToFitText ();
  119. } else {
  120. SetFrameToFitText ();
  121. }
  122. TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
  123. SetNeedsDisplay ();
  124. }
  125. /// <summary>
  126. /// Sets the size of the View to the minimum width or height required to fit <see cref="Text"/>.
  127. /// </summary>
  128. /// <returns><see langword="true"/> if the size was changed; <see langword="false"/> if <see cref="AutoSize"/> == <see langword="true"/> or
  129. /// <see cref="Text"/> will not fit.</returns>
  130. /// <remarks>
  131. /// Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
  132. /// if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
  133. /// Does not take into account word wrapping.
  134. /// </remarks>
  135. bool SetFrameToFitText ()
  136. {
  137. // BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height
  138. // <summary>
  139. // Gets the minimum dimensions required to fit the View's <see cref="Text"/>, factoring in <see cref="TextDirection"/>.
  140. // </summary>
  141. // <param name="sizeRequired">The minimum dimensions required.</param>
  142. // <returns><see langword="true"/> if the dimensions fit within the View's <see cref="Bounds"/>, <see langword="false"/> otherwise.</returns>
  143. // <remarks>
  144. // Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
  145. // if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
  146. // Does not take into account word wrapping.
  147. // </remarks>
  148. bool GetMinimumSizeOfText (out Size sizeRequired)
  149. {
  150. if (!IsInitialized) {
  151. sizeRequired = new Size (0, 0);
  152. return false;
  153. }
  154. sizeRequired = Bounds.Size;
  155. if (!AutoSize && !string.IsNullOrEmpty (TextFormatter.Text)) {
  156. switch (TextFormatter.IsVerticalDirection (TextDirection)) {
  157. case true:
  158. int colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
  159. // TODO: v2 - This uses frame.Width; it should only use Bounds
  160. if (_frame.Width < colWidth &&
  161. (Width == null ||
  162. Bounds.Width >= 0 &&
  163. Width is Dim.DimAbsolute &&
  164. Width.Anchor (0) >= 0 &&
  165. Width.Anchor (0) < colWidth)) {
  166. sizeRequired = new Size (colWidth, Bounds.Height);
  167. return true;
  168. }
  169. break;
  170. default:
  171. if (_frame.Height < 1 &&
  172. (Height == null ||
  173. Height is Dim.DimAbsolute &&
  174. Height.Anchor (0) == 0)) {
  175. sizeRequired = new Size (Bounds.Width, 1);
  176. return true;
  177. }
  178. break;
  179. }
  180. }
  181. return false;
  182. }
  183. if (GetMinimumSizeOfText (out var size)) {
  184. _frame = new Rect (_frame.Location, size);
  185. return true;
  186. }
  187. return false;
  188. }
  189. /// <summary>
  190. /// Gets the width or height of the <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> characters
  191. /// in the <see cref="Text"/> property.
  192. /// </summary>
  193. /// <remarks>
  194. /// Only the first hotkey specifier found in <see cref="Text"/> is supported.
  195. /// </remarks>
  196. /// <param name="isWidth">If <see langword="true"/> (the default) the width required for the hotkey specifier is returned. Otherwise the height is returned.</param>
  197. /// <returns>The number of characters required for the <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/>. If the text direction specified
  198. /// by <see cref="TextDirection"/> does not match the <paramref name="isWidth"/> parameter, <c>0</c> is returned.</returns>
  199. public int GetHotKeySpecifierLength (bool isWidth = true)
  200. {
  201. if (isWidth) {
  202. return TextFormatter.IsHorizontalDirection (TextDirection) &&
  203. TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
  204. ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
  205. } else {
  206. return TextFormatter.IsVerticalDirection (TextDirection) &&
  207. TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
  208. ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
  209. }
  210. }
  211. /// <summary>
  212. /// Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/>.
  213. /// </summary>
  214. /// <returns></returns>
  215. public Size GetSizeNeededForTextWithoutHotKey ()
  216. {
  217. return new Size (TextFormatter.Size.Width - GetHotKeySpecifierLength (),
  218. TextFormatter.Size.Height - GetHotKeySpecifierLength (false));
  219. }
  220. /// <summary>
  221. /// Gets the dimensions required for <see cref="Text"/> accounting for a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> .
  222. /// </summary>
  223. /// <returns></returns>
  224. public Size GetTextFormatterSizeNeededForTextAndHotKey ()
  225. {
  226. if (string.IsNullOrEmpty (TextFormatter.Text)) {
  227. if (!IsInitialized) return Size.Empty;
  228. return Bounds.Size;
  229. }
  230. // BUGBUG: This IGNORES what Text is set to, using on only the current View size. This doesn't seem to make sense.
  231. // BUGBUG: This uses Frame; in v2 it should be Bounds
  232. return new Size (Bounds.Size.Width + GetHotKeySpecifierLength (),
  233. Bounds.Size.Height + GetHotKeySpecifierLength (false));
  234. }
  235. }
  236. }