ViewText.cs 8.7 KB

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