Label.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. //
  2. // Label.cs: Label control
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using NStack;
  11. namespace Terminal.Gui {
  12. /// <summary>
  13. /// Text alignment enumeration, controls how text is displayed.
  14. /// </summary>
  15. public enum TextAlignment {
  16. /// <summary>
  17. /// Aligns the text to the left of the frame.
  18. /// </summary>
  19. Left,
  20. /// <summary>
  21. /// Aligns the text to the right side of the frame.
  22. /// </summary>
  23. Right,
  24. /// <summary>
  25. /// Centers the text in the frame.
  26. /// </summary>
  27. Centered,
  28. /// <summary>
  29. /// Shows the text as justified text in the frame.
  30. /// </summary>
  31. Justified
  32. }
  33. /// <summary>
  34. /// The Label <see cref="View"/> displays a string at a given position and supports multiple lines separted by newline characters.
  35. /// </summary>
  36. public class Label : View {
  37. List<ustring> lines = new List<ustring> ();
  38. bool recalcPending = true;
  39. ustring text;
  40. TextAlignment textAlignment;
  41. static Rect CalcRect (int x, int y, ustring s)
  42. {
  43. int mw = 0;
  44. int ml = 1;
  45. int cols = 0;
  46. foreach (var rune in s) {
  47. if (rune == '\n') {
  48. ml++;
  49. if (cols > mw)
  50. mw = cols;
  51. cols = 0;
  52. } else
  53. cols++;
  54. }
  55. if (cols > mw)
  56. mw = cols;
  57. return new Rect (x, y, mw, ml);
  58. }
  59. /// <summary>
  60. /// Initializes a new instance of <see cref="Label"/> at the given
  61. /// coordinate with the given string, computes the bounding box
  62. /// based on the size of the string, assumes that the string contains
  63. /// newlines for multiple lines, no special breaking rules are used.
  64. /// </summary>
  65. public Label (int x, int y, ustring text) : this (CalcRect (x, y, text), text)
  66. {
  67. }
  68. /// <summary>
  69. /// Initializes a new instance of <see cref="Label"/> at the given
  70. /// coordinate with the given string and uses the specified
  71. /// frame for the string.
  72. /// </summary>
  73. public Label (Rect rect, ustring text) : base (rect)
  74. {
  75. this.text = text;
  76. }
  77. /// <summary>
  78. /// Initializes a new instance of <see cref="Label"/> and configures the default Width and Height based on the text, the result is suitable for Computed layout.
  79. /// </summary>
  80. /// <param name="text">Text.</param>
  81. public Label (ustring text) : base ()
  82. {
  83. this.text = text;
  84. var r = CalcRect (0, 0, text);
  85. Width = r.Width;
  86. Height = r.Height;
  87. }
  88. static char [] whitespace = new char [] { ' ', '\t' };
  89. static ustring ClipAndJustify (ustring str, int width, TextAlignment talign)
  90. {
  91. int slen = str.RuneCount;
  92. if (slen > width){
  93. var uints = str.ToRunes (width);
  94. var runes = new Rune [uints.Length];
  95. for (int i = 0; i < uints.Length; i++)
  96. runes [i] = uints [i];
  97. return ustring.Make (runes);
  98. } else {
  99. if (talign == TextAlignment.Justified) {
  100. // TODO: ustring needs this
  101. var words = str.ToString ().Split (whitespace, StringSplitOptions.RemoveEmptyEntries);
  102. int textCount = words.Sum (arg => arg.Length);
  103. var spaces = (width- textCount) / (words.Length - 1);
  104. var extras = (width - textCount) % words.Length;
  105. var s = new System.Text.StringBuilder ();
  106. //s.Append ($"tc={textCount} sp={spaces},x={extras} - ");
  107. for (int w = 0; w < words.Length; w++) {
  108. var x = words [w];
  109. s.Append (x);
  110. if (w + 1 < words.Length)
  111. for (int i = 0; i < spaces; i++)
  112. s.Append (' ');
  113. if (extras > 0) {
  114. //s.Append ('_');
  115. extras--;
  116. }
  117. }
  118. return ustring.Make (s.ToString ());
  119. }
  120. return str;
  121. }
  122. }
  123. void Recalc ()
  124. {
  125. recalcPending = false;
  126. Recalc (text, lines, Frame.Width, textAlignment);
  127. }
  128. static void Recalc (ustring textStr, List<ustring> lineResult, int width, TextAlignment talign)
  129. {
  130. lineResult.Clear ();
  131. if (textStr.IndexOf ('\n') == -1) {
  132. lineResult.Add (ClipAndJustify (textStr, width, talign));
  133. return;
  134. }
  135. int textLen = textStr.Length;
  136. int lp = 0;
  137. for (int i = 0; i < textLen; i++) {
  138. Rune c = textStr [i];
  139. if (c == '\n') {
  140. lineResult.Add (ClipAndJustify (textStr [lp, i], width, talign));
  141. lp = i + 1;
  142. }
  143. }
  144. lineResult.Add(ClipAndJustify(textStr[lp, textLen], width, talign));
  145. }
  146. ///<inheritdoc cref="Redraw"/>
  147. public override void Redraw (Rect region)
  148. {
  149. if (recalcPending)
  150. Recalc ();
  151. if (TextColor != -1)
  152. Driver.SetAttribute (TextColor);
  153. else
  154. Driver.SetAttribute (ColorScheme.Normal);
  155. Clear ();
  156. for (int line = 0; line < lines.Count; line++) {
  157. if (line < region.Top || line > region.Bottom)
  158. continue;
  159. var str = lines [line];
  160. int x;
  161. switch (textAlignment) {
  162. case TextAlignment.Left:
  163. x = 0;
  164. break;
  165. case TextAlignment.Justified:
  166. Recalc ();
  167. x = Bounds.Left;
  168. break;
  169. case TextAlignment.Right:
  170. x = Bounds.Right - str.Length;
  171. break;
  172. case TextAlignment.Centered:
  173. x = Bounds.Left + (Bounds.Width - str.Length) / 2;
  174. break;
  175. default:
  176. throw new ArgumentOutOfRangeException ();
  177. }
  178. Move (x, line);
  179. Driver.AddStr (str);
  180. }
  181. }
  182. /// <summary>
  183. /// Computes the number of lines needed to render the specified text by the <see cref="Label"/> view
  184. /// </summary>
  185. /// <returns>Number of lines.</returns>
  186. /// <param name="text">Text, may contain newlines.</param>
  187. /// <param name="width">The width for the text.</param>
  188. public static int MeasureLines (ustring text, int width)
  189. {
  190. var result = new List<ustring> ();
  191. Recalc (text, result, width, TextAlignment.Left);
  192. return result.Count;
  193. }
  194. /// <summary>
  195. /// Computes the the max width of a line or multilines needed to render by the Label control
  196. /// </summary>
  197. /// <returns>Max width of lines.</returns>
  198. /// <param name="text">Text, may contain newlines.</param>
  199. /// <param name="width">The width for the text.</param>
  200. public static int MaxWidth(ustring text, int width)
  201. {
  202. var result = new List<ustring>();
  203. Recalc(text, result, width, TextAlignment.Left);
  204. return result.Max(s => s.RuneCount);
  205. }
  206. /// <summary>
  207. /// The text displayed by the <see cref="Label"/>.
  208. /// </summary>
  209. public virtual ustring Text {
  210. get => text;
  211. set {
  212. text = value;
  213. recalcPending = true;
  214. SetNeedsDisplay ();
  215. }
  216. }
  217. /// <summary>
  218. /// Controls the text-alignemtn property of the label, changing it will redisplay the <see cref="Label"/>.
  219. /// </summary>
  220. /// <value>The text alignment.</value>
  221. public TextAlignment TextAlignment {
  222. get => textAlignment;
  223. set {
  224. textAlignment = value;
  225. SetNeedsDisplay ();
  226. }
  227. }
  228. Attribute textColor = -1;
  229. /// <summary>
  230. /// The color used for the <see cref="Label"/>.
  231. /// </summary>
  232. public Attribute TextColor {
  233. get => textColor;
  234. set {
  235. textColor = value;
  236. SetNeedsDisplay ();
  237. }
  238. }
  239. }
  240. }