Label.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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. /// The Label <see cref="View"/> displays a string at a given position and supports multiple lines separted by newline characters.
  14. /// </summary>
  15. public class Label : View {
  16. List<ustring> lines = new List<ustring> ();
  17. bool recalcPending = true;
  18. ustring text;
  19. TextAlignment textAlignment;
  20. static Rect CalcRect (int x, int y, ustring s)
  21. {
  22. int mw = 0;
  23. int ml = 1;
  24. int cols = 0;
  25. foreach (var rune in s) {
  26. if (rune == '\n') {
  27. ml++;
  28. if (cols > mw)
  29. mw = cols;
  30. cols = 0;
  31. } else
  32. cols++;
  33. }
  34. if (cols > mw)
  35. mw = cols;
  36. return new Rect (x, y, mw, ml);
  37. }
  38. /// <summary>
  39. /// Initializes a new instance of <see cref="Label"/> using <see cref="LayoutStyle.Absolute"/> layout.
  40. /// </summary>
  41. /// <remarks>
  42. /// <para>
  43. /// The <see cref="Label"/> will be created at the given
  44. /// coordinates with the given string. The size (<see cref="View.Frame"/> will be
  45. /// adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
  46. /// </para>
  47. /// <para>
  48. /// No line wraping is provided.
  49. /// </para>
  50. /// </remarks>
  51. /// <param name="x">column to locate the Label.</param>
  52. /// <param name="y">row to locate the Label.</param>
  53. /// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
  54. public Label (int x, int y, ustring text) : this (CalcRect (x, y, text), text)
  55. {
  56. }
  57. /// <summary>
  58. /// Initializes a new instance of <see cref="Label"/> using <see cref="LayoutStyle.Absolute"/> layout.
  59. /// </summary>
  60. /// <remarks>
  61. /// <para>
  62. /// The <see cref="Label"/> will be created at the given
  63. /// coordinates with the given string. The initial size (<see cref="View.Frame"/> will be
  64. /// adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
  65. /// </para>
  66. /// <para>
  67. /// No line wraping is provided.
  68. /// </para>
  69. /// </remarks>
  70. /// <param name="rect">Location.</param>
  71. /// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
  72. public Label (Rect rect, ustring text) : base (rect)
  73. {
  74. this.text = text;
  75. }
  76. /// <summary>
  77. /// Initializes a new instance of <see cref="Label"/> using <see cref="LayoutStyle.Computed"/> layout.
  78. /// </summary>
  79. /// <remarks>
  80. /// <para>
  81. /// The <see cref="Label"/> will be created using <see cref="LayoutStyle.Computed"/>
  82. /// coordinates with the given string. The initial size (<see cref="View.Frame"/> will be
  83. /// adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
  84. /// </para>
  85. /// <para>
  86. /// No line wraping is provided.
  87. /// </para>
  88. /// </remarks>
  89. /// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
  90. public Label (ustring text) : base ()
  91. {
  92. this.text = text;
  93. var r = CalcRect (0, 0, text);
  94. Width = r.Width;
  95. Height = r.Height;
  96. }
  97. /// <summary>
  98. /// Initializes a new instance of <see cref="Label"/> using <see cref="LayoutStyle.Computed"/> layout.
  99. /// </summary>
  100. /// <remarks>
  101. /// <para>
  102. /// The <see cref="Label"/> will be created using <see cref="LayoutStyle.Computed"/>
  103. /// coordinates. The initial size (<see cref="View.Frame"/> will be
  104. /// adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
  105. /// </para>
  106. /// <para>
  107. /// No line wraping is provided.
  108. /// </para>
  109. /// </remarks>
  110. public Label () : this (text: string.Empty) { }
  111. static char [] whitespace = new char [] { ' ', '\t' };
  112. static ustring ClipAndJustify (ustring str, int width, TextAlignment talign)
  113. {
  114. // Get rid of any '\r' added by Windows
  115. str = str.Replace ("\r", ustring.Empty);
  116. int slen = str.RuneCount;
  117. if (slen > width){
  118. var uints = str.ToRunes (width);
  119. var runes = new Rune [uints.Length];
  120. for (int i = 0; i < uints.Length; i++)
  121. runes [i] = uints [i];
  122. return ustring.Make (runes);
  123. } else {
  124. if (talign == TextAlignment.Justified) {
  125. // TODO: ustring needs this
  126. var words = str.ToString ().Split (whitespace, StringSplitOptions.RemoveEmptyEntries);
  127. int textCount = words.Sum (arg => arg.Length);
  128. var spaces = (width- textCount) / (words.Length - 1);
  129. var extras = (width - textCount) % words.Length;
  130. var s = new System.Text.StringBuilder ();
  131. //s.Append ($"tc={textCount} sp={spaces},x={extras} - ");
  132. for (int w = 0; w < words.Length; w++) {
  133. var x = words [w];
  134. s.Append (x);
  135. if (w + 1 < words.Length)
  136. for (int i = 0; i < spaces; i++)
  137. s.Append (' ');
  138. if (extras > 0) {
  139. //s.Append ('_');
  140. extras--;
  141. }
  142. }
  143. return ustring.Make (s.ToString ());
  144. }
  145. return str;
  146. }
  147. }
  148. void Recalc ()
  149. {
  150. recalcPending = false;
  151. Recalc (text, lines, Frame.Width, textAlignment);
  152. }
  153. static void Recalc (ustring textStr, List<ustring> lineResult, int width, TextAlignment talign)
  154. {
  155. lineResult.Clear ();
  156. if (textStr.IndexOf ('\n') == -1) {
  157. lineResult.Add (ClipAndJustify (textStr, width, talign));
  158. return;
  159. }
  160. int textLen = textStr.Length;
  161. int lp = 0;
  162. for (int i = 0; i < textLen; i++) {
  163. Rune c = textStr [i];
  164. if (c == '\n') {
  165. lineResult.Add (ClipAndJustify (textStr [lp, i], width, talign));
  166. lp = i + 1;
  167. }
  168. }
  169. lineResult.Add(ClipAndJustify(textStr[lp, textLen], width, talign));
  170. }
  171. ///<inheritdoc/>
  172. public override void Redraw (Rect bounds)
  173. {
  174. if (recalcPending)
  175. Recalc ();
  176. if (TextColor != -1)
  177. Driver.SetAttribute (TextColor);
  178. else
  179. Driver.SetAttribute (ColorScheme.Normal);
  180. Clear ();
  181. for (int line = 0; line < lines.Count; line++) {
  182. if (line < bounds.Top || line > bounds.Bottom)
  183. continue;
  184. var str = lines [line];
  185. int x;
  186. switch (textAlignment) {
  187. case TextAlignment.Left:
  188. x = 0;
  189. break;
  190. case TextAlignment.Justified:
  191. Recalc ();
  192. x = Bounds.Left;
  193. break;
  194. case TextAlignment.Right:
  195. x = Bounds.Right - str.Length;
  196. break;
  197. case TextAlignment.Centered:
  198. x = Bounds.Left + (Bounds.Width - str.Length) / 2;
  199. break;
  200. default:
  201. throw new ArgumentOutOfRangeException ();
  202. }
  203. Move (x, line);
  204. Driver.AddStr (str);
  205. }
  206. }
  207. /// <summary>
  208. /// Computes the number of lines needed to render the specified text by the <see cref="Label"/> view
  209. /// </summary>
  210. /// <returns>Number of lines.</returns>
  211. /// <param name="text">Text, may contain newlines.</param>
  212. /// <param name="width">The width for the text.</param>
  213. public static int MeasureLines (ustring text, int width)
  214. {
  215. var result = new List<ustring> ();
  216. Recalc (text, result, width, TextAlignment.Left);
  217. return result.Count;
  218. }
  219. /// <summary>
  220. /// Computes the max width of a line or multilines needed to render by the Label control
  221. /// </summary>
  222. /// <returns>Max width of lines.</returns>
  223. /// <param name="text">Text, may contain newlines.</param>
  224. /// <param name="width">The width for the text.</param>
  225. public static int MaxWidth(ustring text, int width)
  226. {
  227. var result = new List<ustring>();
  228. Recalc(text, result, width, TextAlignment.Left);
  229. return result.Max(s => s.RuneCount);
  230. }
  231. /// <summary>
  232. /// The text displayed by the <see cref="Label"/>.
  233. /// </summary>
  234. public virtual ustring Text {
  235. get => text;
  236. set {
  237. text = value;
  238. recalcPending = true;
  239. SetNeedsDisplay ();
  240. }
  241. }
  242. /// <summary>
  243. /// Controls the text-alignment property of the label, changing it will redisplay the <see cref="Label"/>.
  244. /// </summary>
  245. /// <value>The text alignment.</value>
  246. public TextAlignment TextAlignment {
  247. get => textAlignment;
  248. set {
  249. textAlignment = value;
  250. SetNeedsDisplay ();
  251. }
  252. }
  253. Attribute textColor = -1;
  254. /// <summary>
  255. /// The color used for the <see cref="Label"/>.
  256. /// </summary>
  257. public Attribute TextColor {
  258. get => textColor;
  259. set {
  260. textColor = value;
  261. SetNeedsDisplay ();
  262. }
  263. }
  264. }
  265. }