Thickness.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. using NStack;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. using System.Text.Json.Serialization;
  6. using Terminal.Gui;
  7. namespace Terminal.Gui {
  8. /// <summary>
  9. /// Describes the thickness of a frame around a rectangle. Four <see cref="int"/> values describe
  10. /// the <see cref="Left"/>, <see cref="Top"/>, <see cref="Right"/>, and <see cref="Bottom"/> sides
  11. /// of the rectangle, respectively.
  12. /// </summary>
  13. /// <remarks>
  14. /// <para>
  15. /// Use the helper API (<see cref="GetInside(Rect)"/> to get the rectangle describing the insides of the frame,
  16. /// with the thickness widths subtracted.
  17. /// </para>
  18. /// <para>
  19. /// Use the helper API (<see cref="Draw(Rect, string)"/> to draw the frame with the specified thickness.
  20. /// </para>
  21. /// </remarks>
  22. public class Thickness : IEquatable<Thickness> {
  23. private int validate (int width)
  24. {
  25. if (width < 0) {
  26. throw new ArgumentException ("Thickness widths cannot be negative.");
  27. }
  28. return width;
  29. }
  30. /// <summary>
  31. /// Gets or sets the width of the left side of the rectangle.
  32. /// </summary>
  33. [JsonInclude]
  34. public int Left;
  35. /// <summary>
  36. /// Gets or sets the width of the upper side of the rectangle.
  37. /// </summary>
  38. [JsonInclude]
  39. public int Top;
  40. /// <summary>
  41. /// Gets or sets the width of the right side of the rectangle.
  42. /// </summary>
  43. [JsonInclude]
  44. public int Right;
  45. /// <summary>
  46. /// Gets or sets the width of the lower side of the rectangle.
  47. /// </summary>
  48. [JsonInclude]
  49. public int Bottom;
  50. /// <summary>
  51. /// Initializes a new instance of the <see cref="Thickness"/> class with all widths
  52. /// set to 0.
  53. /// </summary>
  54. public Thickness () { }
  55. /// <summary>
  56. /// Initializes a new instance of the <see cref="Thickness"/> class with a uniform width to each side.
  57. /// </summary>
  58. /// <param name="width"></param>
  59. public Thickness (int width) : this (width, width, width, width) { }
  60. /// <summary>
  61. /// Initializes a new instance of the <see cref="Thickness"/> class that has specific
  62. /// widths applied to each side of the rectangle.
  63. /// </summary>
  64. /// <param name="left"></param>
  65. /// <param name="top"></param>
  66. /// <param name="right"></param>
  67. /// <param name="bottom"></param>
  68. public Thickness (int left, int top, int right, int bottom)
  69. {
  70. Left = left;
  71. Top = top;
  72. Right = right;
  73. Bottom = bottom;
  74. }
  75. /// <summary>
  76. /// Gets the total width of the left and right sides of the rectangle. Sets the height of the left and right sides of the rectangle to half the specified value.
  77. /// </summary>
  78. public int Vertical {
  79. get {
  80. return Top + Bottom;
  81. }
  82. set {
  83. Top = Bottom = value / 2;
  84. }
  85. }
  86. /// <summary>
  87. /// Gets the total width of the top and bottom sides of the rectangle. Sets the width of the top and bottom sides of the rectangle to half the specified value.
  88. /// </summary>
  89. public int Horizontal {
  90. get {
  91. return Left + Right;
  92. }
  93. set {
  94. Left = Right = value / 2;
  95. }
  96. }
  97. /// <summary>
  98. /// Returns a rectangle describing the location and size of the inside area of <paramref name="rect"/>
  99. /// with the thickness widths subtracted. The height and width of the returned rectangle will
  100. /// never be less than 0.
  101. /// </summary>
  102. /// <remarks>If a thickness width is negative, the inside rectangle will be larger than <paramref name="rect"/>. e.g.
  103. /// a <c>Thickness (-1, -1, -1, -1) will result in a rectangle skewed -1 in the X and Y directions and
  104. /// with a Size increased by 1.</c></remarks>
  105. /// <param name="rect">The source rectangle</param>
  106. /// <returns></returns>
  107. public Rect GetInside (Rect rect)
  108. {
  109. var x = rect.X + Left;
  110. var y = rect.Y + Top;
  111. var width = Math.Max (0, rect.Size.Width - Horizontal);
  112. var height = Math.Max (0, rect.Size.Height - Vertical);
  113. return new Rect (new Point (x, y), new Size (width, height));
  114. }
  115. /// <summary>
  116. /// Draws the <see cref="Thickness"/> rectangle with an optional diagnostics label.
  117. /// </summary>
  118. /// <remarks>
  119. /// If <see cref="ConsoleDriver.DiagnosticFlags"/> is set to <see cref="ConsoleDriver.DiagnosticFlags.FramePadding"/> then
  120. /// 'T', 'L', 'R', and 'B' glyphs will be used instead of space. If <see cref="ConsoleDriver.DiagnosticFlags"/>
  121. /// is set to <see cref="ConsoleDriver.DiagnosticFlags.FrameRuler"/> then a ruler will be drawn on the outer edge of the
  122. /// Thickness.
  123. /// </remarks>
  124. /// <param name="rect">The location and size of the rectangle that bounds the thickness rectangle, in
  125. /// screen coordinates.</param>
  126. /// <param name="label">The diagnostics label to draw on the bottom of the <see cref="Bottom"/>.</param>
  127. /// <returns>The inner rectangle remaining to be drawn.</returns>
  128. public Rect Draw (Rect rect, string label = null)
  129. {
  130. if (rect.Size.Width < 1 || rect.Size.Height < 1) {
  131. return Rect.Empty;
  132. }
  133. System.Rune clearChar = ' ';
  134. System.Rune leftChar = clearChar;
  135. System.Rune rightChar = clearChar;
  136. System.Rune topChar = clearChar;
  137. System.Rune bottomChar = clearChar;
  138. if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding) == ConsoleDriver.DiagnosticFlags.FramePadding) {
  139. leftChar = 'L';
  140. rightChar = 'R';
  141. topChar = 'T';
  142. bottomChar = 'B';
  143. if (!string.IsNullOrEmpty (label)) {
  144. leftChar = rightChar = bottomChar = topChar = label [0];
  145. }
  146. }
  147. // Draw the Top side
  148. if (Top > 0) {
  149. Application.Driver.FillRect (new Rect (rect.X, rect.Y, rect.Width, Math.Min (rect.Height, Top)), topChar);
  150. }
  151. // Draw the Left side
  152. if (Left > 0) {
  153. Application.Driver.FillRect (new Rect (rect.X, rect.Y, Math.Min (rect.Width, Left), rect.Height), leftChar);
  154. }
  155. // Draw the Right side
  156. if (Right > 0) {
  157. Application.Driver.FillRect (new Rect (Math.Max (0, rect.X + rect.Width - Right), rect.Y, Math.Min (rect.Width, Right), rect.Height), rightChar);
  158. }
  159. // Draw the Bottom side
  160. if (Bottom > 0) {
  161. Application.Driver.FillRect (new Rect (rect.X, rect.Y + Math.Max (0, rect.Height - Bottom), rect.Width, Bottom), bottomChar);
  162. }
  163. // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
  164. if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) {
  165. // Top
  166. var hruler = new Ruler () { Length = rect.Width, Orientation = Orientation.Horizontal };
  167. if (Top > 0) {
  168. hruler.Draw (new Point (rect.X, rect.Y));
  169. }
  170. //Left
  171. var vruler = new Ruler () { Length = rect.Height - 2, Orientation = Orientation.Vertical };
  172. if (Left > 0) {
  173. vruler.Draw (new Point (rect.X, rect.Y + 1), 1);
  174. }
  175. // Bottom
  176. if (Bottom > 0) {
  177. hruler.Draw (new Point (rect.X, rect.Y + rect.Height - 1));
  178. }
  179. // Right
  180. if (Right > 0) {
  181. vruler.Draw (new Point (rect.X + rect.Width - 1, rect.Y + 1), 1);
  182. }
  183. }
  184. if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding) == ConsoleDriver.DiagnosticFlags.FramePadding) {
  185. // Draw the diagnostics label on the bottom
  186. var tf = new TextFormatter () {
  187. Text = label == null ? string.Empty : $"{label} {this}",
  188. Alignment = TextAlignment.Centered,
  189. VerticalAlignment = VerticalTextAlignment.Bottom
  190. };
  191. tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute, rect, false);
  192. }
  193. return GetInside (rect);
  194. }
  195. // TODO: add operator overloads
  196. /// <summary>
  197. /// Gets an empty thickness.
  198. /// </summary>
  199. public static Thickness Empty => new Thickness (0);
  200. /// <inheritdoc/>
  201. public override bool Equals (object obj)
  202. {
  203. //Check for null and compare run-time types.
  204. if ((obj == null) || !this.GetType ().Equals (obj.GetType ())) {
  205. return false;
  206. } else {
  207. return Equals ((Thickness)obj);
  208. }
  209. }
  210. /// <summary>Returns the thickness widths of the Thickness formatted as a string.</summary>
  211. /// <returns>The thickness widths as a string.</returns>
  212. public override string ToString ()
  213. {
  214. return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})";
  215. }
  216. // IEquitable
  217. /// <inheritdoc/>
  218. public bool Equals (Thickness other)
  219. {
  220. return other is not null &&
  221. Left == other.Left &&
  222. Right == other.Right &&
  223. Top == other.Top &&
  224. Bottom == other.Bottom;
  225. }
  226. /// <inheritdoc/>
  227. public override int GetHashCode ()
  228. {
  229. int hashCode = 1380952125;
  230. hashCode = hashCode * -1521134295 + Left.GetHashCode ();
  231. hashCode = hashCode * -1521134295 + Right.GetHashCode ();
  232. hashCode = hashCode * -1521134295 + Top.GetHashCode ();
  233. hashCode = hashCode * -1521134295 + Bottom.GetHashCode ();
  234. return hashCode;
  235. }
  236. /// <inheritdoc/>
  237. public static bool operator == (Thickness left, Thickness right)
  238. {
  239. return EqualityComparer<Thickness>.Default.Equals (left, right);
  240. }
  241. /// <inheritdoc/>
  242. public static bool operator != (Thickness left, Thickness right)
  243. {
  244. return !(left == right);
  245. }
  246. }
  247. }