Cell.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #nullable enable
  2. namespace Terminal.Gui;
  3. /// <summary>
  4. /// Represents a single row/column in a Terminal.Gui rendering surface (e.g. <see cref="LineCanvas"/> and
  5. /// <see cref="IConsoleDriver"/>).
  6. /// </summary>
  7. public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Rune Rune = default)
  8. {
  9. /// <summary>The attributes to use when drawing the Glyph.</summary>
  10. public Attribute? Attribute { get; set; } = Attribute;
  11. /// <summary>
  12. /// Gets or sets a value indicating whether this <see cref="T:Terminal.Gui.Cell"/> has been modified since the
  13. /// last time it was drawn.
  14. /// </summary>
  15. public bool IsDirty { get; set; } = IsDirty;
  16. private Rune _rune = Rune;
  17. /// <summary>The character to display. If <see cref="Rune"/> is <see langword="null"/>, then <see cref="Rune"/> is ignored.</summary>
  18. public Rune Rune
  19. {
  20. get => _rune;
  21. set
  22. {
  23. _combiningMarks?.Clear ();
  24. _rune = value;
  25. }
  26. }
  27. private List<Rune>? _combiningMarks;
  28. /// <summary>
  29. /// The combining marks for <see cref="Rune"/> that when combined makes this Cell a combining sequence. If
  30. /// <see cref="CombiningMarks"/> empty, then <see cref="CombiningMarks"/> is ignored.
  31. /// </summary>
  32. /// <remarks>
  33. /// Only valid in the rare case where <see cref="Rune"/> is a combining sequence that could not be normalized to a
  34. /// single Rune.
  35. /// </remarks>
  36. internal IReadOnlyList<Rune> CombiningMarks
  37. {
  38. // PERFORMANCE: Downside of the interface return type is that List<T> struct enumerator cannot be utilized, i.e. enumerator is allocated.
  39. // If enumeration is used heavily in the future then might be better to expose the List<T> Enumerator directly via separate mechanism.
  40. get
  41. {
  42. // Avoid unnecessary list allocation.
  43. if (_combiningMarks == null)
  44. {
  45. return Array.Empty<Rune> ();
  46. }
  47. return _combiningMarks;
  48. }
  49. }
  50. /// <summary>
  51. /// Adds combining mark to the cell.
  52. /// </summary>
  53. /// <param name="combiningMark">The combining mark to add to the cell.</param>
  54. internal void AddCombiningMark (Rune combiningMark)
  55. {
  56. _combiningMarks ??= [];
  57. _combiningMarks.Add (combiningMark);
  58. }
  59. /// <summary>
  60. /// Clears combining marks of the cell.
  61. /// </summary>
  62. internal void ClearCombiningMarks ()
  63. {
  64. _combiningMarks?.Clear ();
  65. }
  66. /// <inheritdoc/>
  67. public override string ToString () { return $"[{Rune}, {Attribute}]"; }
  68. /// <summary>Converts the string into a <see cref="List{Cell}"/>.</summary>
  69. /// <param name="str">The string to convert.</param>
  70. /// <param name="attribute">The <see cref="Gui.ColorScheme"/> to use.</param>
  71. /// <returns></returns>
  72. public static List<Cell> ToCellList (string str, Attribute? attribute = null)
  73. {
  74. List<Cell> cells = new ();
  75. foreach (Rune rune in str.EnumerateRunes ())
  76. {
  77. cells.Add (new () { Rune = rune, Attribute = attribute });
  78. }
  79. return cells;
  80. }
  81. /// <summary>
  82. /// Splits a string into a List that will contain a <see cref="List{Cell}"/> for each line.
  83. /// </summary>
  84. /// <param name="content">The string content.</param>
  85. /// <param name="attribute">The color scheme.</param>
  86. /// <returns>A <see cref="List{Cell}"/> for each line.</returns>
  87. public static List<List<Cell>> StringToLinesOfCells (string content, Attribute? attribute = null)
  88. {
  89. List<Cell> cells = content.EnumerateRunes ()
  90. .Select (x => new Cell { Rune = x, Attribute = attribute })
  91. .ToList ();
  92. return SplitNewLines (cells);
  93. }
  94. /// <summary>Converts a <see cref="Cell"/> generic collection into a string.</summary>
  95. /// <param name="cells">The enumerable cell to convert.</param>
  96. /// <returns></returns>
  97. public static string ToString (IEnumerable<Cell> cells)
  98. {
  99. var str = string.Empty;
  100. foreach (Cell cell in cells)
  101. {
  102. str += cell.Rune.ToString ();
  103. }
  104. return str;
  105. }
  106. /// <summary>Converts a <see cref="List{Cell}"/> generic collection into a string.</summary>
  107. /// <param name="cellsList">The enumerable cell to convert.</param>
  108. /// <returns></returns>
  109. public static string ToString (List<List<Cell>> cellsList)
  110. {
  111. var str = string.Empty;
  112. for (var i = 0; i < cellsList.Count; i++)
  113. {
  114. IEnumerable<Cell> cellList = cellsList [i];
  115. str += ToString (cellList);
  116. if (i + 1 < cellsList.Count)
  117. {
  118. str += Environment.NewLine;
  119. }
  120. }
  121. return str;
  122. }
  123. // Turns the string into cells, this does not split the contents on a newline if it is present.
  124. internal static List<Cell> StringToCells (string str, Attribute? attribute = null)
  125. {
  126. List<Cell> cells = [];
  127. foreach (Rune rune in str.ToRunes ())
  128. {
  129. cells.Add (new () { Rune = rune, Attribute = attribute });
  130. }
  131. return cells;
  132. }
  133. internal static List<Cell> ToCells (IEnumerable<Rune> runes, Attribute? attribute = null)
  134. {
  135. List<Cell> cells = new ();
  136. foreach (Rune rune in runes)
  137. {
  138. cells.Add (new () { Rune = rune, Attribute = attribute });
  139. }
  140. return cells;
  141. }
  142. private static List<List<Cell>> SplitNewLines (List<Cell> cells)
  143. {
  144. List<List<Cell>> lines = [];
  145. int start = 0, i = 0;
  146. var hasCR = false;
  147. // ASCII code 13 = Carriage Return.
  148. // ASCII code 10 = Line Feed.
  149. for (; i < cells.Count; i++)
  150. {
  151. if (cells [i].Rune.Value == 13)
  152. {
  153. hasCR = true;
  154. continue;
  155. }
  156. if (cells [i].Rune.Value == 10)
  157. {
  158. if (i - start > 0)
  159. {
  160. lines.Add (cells.GetRange (start, hasCR ? i - 1 - start : i - start));
  161. }
  162. else
  163. {
  164. lines.Add (StringToCells (string.Empty));
  165. }
  166. start = i + 1;
  167. hasCR = false;
  168. }
  169. }
  170. if (i - start >= 0)
  171. {
  172. lines.Add (cells.GetRange (start, i - start));
  173. }
  174. return lines;
  175. }
  176. /// <summary>
  177. /// Splits a rune cell list into a List that will contain a <see cref="List{Cell}"/> for each line.
  178. /// </summary>
  179. /// <param name="cells">The cells list.</param>
  180. /// <returns></returns>
  181. public static List<List<Cell>> ToCells (List<Cell> cells) { return SplitNewLines (cells); }
  182. }