ViewText.cs 9.0 KB

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