Justification.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. namespace Terminal.Gui;
  2. /// <summary>
  3. /// Controls how items are justified within a container. Used by <see cref="Justifier"/>.
  4. /// </summary>
  5. public enum Justification
  6. {
  7. /// <summary>
  8. /// The items will be left-justified.
  9. /// </summary>
  10. Left,
  11. /// <summary>
  12. /// The items will be right-justified.
  13. /// </summary>
  14. Right,
  15. /// <summary>
  16. /// The items will be arranged such that there is no more than 1 space between them. The group will be centered in the container.
  17. /// </summary>
  18. Centered,
  19. /// <summary>
  20. /// The items will be justified. Space will be added between the items such that the first item
  21. /// is at the start and the right side of the last item against the end.
  22. /// </summary>
  23. /// <example>
  24. /// <c>
  25. /// 111 2222 33333
  26. /// </c>
  27. /// </example>
  28. Justified,
  29. /// <summary>
  30. /// The items will be left-justified. The first item will be at the start and the last item will be at the end.
  31. /// Those in between will be tight against the right item.
  32. /// </summary>
  33. /// <example>
  34. /// <c>
  35. /// 111 2222 33333
  36. /// </c>
  37. /// </example>
  38. RightJustified,
  39. /// <summary>
  40. /// The items will be left-justified. The first item will be at the start and the last item will be at the end.
  41. /// Those in between will be tight against the right item.
  42. /// </summary>
  43. /// <example>
  44. /// <c>
  45. /// 111 2222 33333
  46. /// </c>
  47. /// </example>
  48. LeftJustified
  49. }
  50. /// <summary>
  51. /// Justifies items within a container based on the specified <see cref="Justification"/>.
  52. /// </summary>
  53. public class Justifier
  54. {
  55. /// <summary>
  56. /// Justifies the <paramref name="sizes"/> within a container <see cref="totalSize"/> wide based on the specified
  57. /// <see cref="Justification"/>.
  58. /// </summary>
  59. /// <param name="sizes"></param>
  60. /// <param name="justification"></param>
  61. /// <param name="totalSize"></param>
  62. /// <returns></returns>
  63. public static int [] Justify (int [] sizes, Justification justification, int totalSize)
  64. {
  65. var positions = new int [sizes.Length];
  66. int totalItemsSize = sizes.Sum ();
  67. if (totalItemsSize > totalSize)
  68. {
  69. throw new ArgumentException ("The sum of the sizes is greater than the total size.");
  70. }
  71. switch (justification)
  72. {
  73. case Justification.Left:
  74. var currentPosition = 0;
  75. for (var i = 0; i < sizes.Length; i++)
  76. {
  77. if (sizes [i] < 0)
  78. {
  79. throw new ArgumentException ("The size of an item cannot be negative.");
  80. }
  81. positions [i] = currentPosition;
  82. currentPosition += sizes [i];
  83. }
  84. break;
  85. case Justification.Right:
  86. currentPosition = totalSize - totalItemsSize;
  87. for (var i = 0; i < sizes.Length; i++)
  88. {
  89. if (sizes [i] < 0)
  90. {
  91. throw new ArgumentException ("The size of an item cannot be negative.");
  92. }
  93. positions [i] = currentPosition;
  94. currentPosition += sizes [i];
  95. }
  96. break;
  97. case Justification.Centered:
  98. if (sizes.Length > 1)
  99. {
  100. totalItemsSize = sizes.Sum (); // total size of items
  101. int totalGaps = sizes.Length - 1; // total gaps (0 or 1 space)
  102. int totalItemsAndSpaces = totalItemsSize + totalGaps; // total size of items and spaces
  103. int spaces = totalGaps;
  104. if (totalItemsSize >= totalSize)
  105. {
  106. spaces = 0;
  107. }
  108. else if (totalItemsAndSpaces > totalSize)
  109. {
  110. spaces = totalItemsAndSpaces - totalSize;
  111. }
  112. int remainingSpace = Math.Max(0, totalSize - totalItemsSize - spaces); // remaining space to be distributed before and after the items
  113. int spaceBefore = remainingSpace / 2; // space before the items
  114. positions [0] = spaceBefore; // first item position
  115. for (var i = 1; i < sizes.Length; i++)
  116. {
  117. int aSpace = 0;
  118. if (spaces > 0)
  119. {
  120. spaces--;
  121. aSpace = 1;
  122. }
  123. // subsequent items are placed one space after the previous item
  124. positions [i] = positions [i - 1] + sizes [i - 1] + aSpace;
  125. }
  126. // Adjust the last position if there is an extra space
  127. if (positions [sizes.Length - 1] + sizes [sizes.Length - 1] > totalSize)
  128. {
  129. positions [sizes.Length - 1]--;
  130. }
  131. }
  132. else if (sizes.Length == 1)
  133. {
  134. positions [0] = (totalSize - sizes [0]) / 2; // single item is centered
  135. }
  136. break;
  137. case Justification.Justified:
  138. int spaceBetween = sizes.Length > 1 ? (totalSize - totalItemsSize) / (sizes.Length - 1) : 0;
  139. int remainder = sizes.Length > 1 ? (totalSize - totalItemsSize) % (sizes.Length - 1) : 0;
  140. currentPosition = 0;
  141. for (var i = 0; i < sizes.Length; i++)
  142. {
  143. if (sizes [i] < 0)
  144. {
  145. throw new ArgumentException ("The size of an item cannot be negative.");
  146. }
  147. positions [i] = currentPosition;
  148. int extraSpace = i < remainder ? 1 : 0;
  149. currentPosition += sizes [i] + spaceBetween + extraSpace;
  150. }
  151. break;
  152. case Justification.LeftJustified:
  153. if (sizes.Length > 1)
  154. {
  155. int spaceBetweenLeft = totalSize - sizes.Sum () + 1; // +1 for the extra space
  156. currentPosition = 0;
  157. for (var i = 0; i < sizes.Length - 1; i++)
  158. {
  159. if (sizes [i] < 0)
  160. {
  161. throw new ArgumentException ("The size of an item cannot be negative.");
  162. }
  163. positions [i] = currentPosition;
  164. currentPosition += sizes [i] + 1; // +1 for the extra space
  165. }
  166. positions [sizes.Length - 1] = totalSize - sizes [sizes.Length - 1];
  167. }
  168. else if (sizes.Length == 1)
  169. {
  170. positions [0] = 0;
  171. }
  172. break;
  173. case Justification.RightJustified:
  174. if (sizes.Length > 1)
  175. {
  176. totalItemsSize = sizes.Sum ();
  177. int totalSpaces = totalSize - totalItemsSize;
  178. int bigSpace = totalSpaces - (sizes.Length - 2);
  179. positions [0] = 0; // first item is flush left
  180. positions [1] = sizes [0] + bigSpace; // second item has the big space before it
  181. // remaining items have one space between them
  182. for (var i = 2; i < sizes.Length; i++)
  183. {
  184. positions [i] = positions [i - 1] + sizes [i - 1] + 1;
  185. }
  186. }
  187. else if (sizes.Length == 1)
  188. {
  189. positions [0] = 0; // single item is flush left
  190. }
  191. break;
  192. }
  193. return positions;
  194. }
  195. }