Thickness.cs 9.3 KB

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