Label.cs 8.6 KB

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