StraightLineCanvas.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace Terminal.Gui.Graphs {
  6. /// <summary>
  7. /// Facilitates box drawing and line intersection detection
  8. /// and rendering.
  9. /// </summary>
  10. public class StraightLineCanvas {
  11. private List<StraightLine> lines = new List<StraightLine> ();
  12. private ConsoleDriver driver;
  13. public StraightLineCanvas (ConsoleDriver driver)
  14. {
  15. this.driver = driver;
  16. }
  17. /// <summary>
  18. /// Add a new line to the canvas starting at <paramref name="from"/>.
  19. /// Use positive <paramref name="length"/> for Right and negative for Left
  20. /// when <see cref="Orientation"/> is <see cref="Orientation.Horizontal"/>.
  21. /// Use positive <paramref name="length"/> for Down and negative for Up
  22. /// when <see cref="Orientation"/> is <see cref="Orientation.Vertical"/>.
  23. /// </summary>
  24. /// <param name="from">Starting point.</param>
  25. /// <param name="length">Length of line. 0 for a dot.
  26. /// Positive for Down/Right. Negative for Up/Left.</param>
  27. /// <param name="orientation">Direction of the line.</param>
  28. public void AddLine (Point from, int length, Orientation orientation, BorderStyle style)
  29. {
  30. lines.Add (new StraightLine (from, length, orientation, style));
  31. }
  32. /// <summary>
  33. /// Evaluate all currently defined lines that lie within
  34. /// <paramref name="inArea"/> and generate a 'bitmap' that
  35. /// shows what characters (if any) should be rendered at each
  36. /// point so that all lines connect up correctly with appropriate
  37. /// intersection symbols.
  38. /// <returns></returns>
  39. /// </summary>
  40. /// <param name="inArea"></param>
  41. /// <returns>Map as 2D array where first index is rows and second is column</returns>
  42. public Rune? [,] GenerateImage (Rect inArea)
  43. {
  44. Rune? [,] canvas = new Rune? [inArea.Height, inArea.Width];
  45. // walk through each pixel of the bitmap
  46. for (int y = 0; y < inArea.Height; y++) {
  47. for (int x = 0; x < inArea.Width; x++) {
  48. var intersects = lines
  49. .Select (l => l.Intersects (x, y))
  50. .Where(i=>i != null)
  51. .ToArray();
  52. // TODO: use Driver and LineStyle to map
  53. canvas [x, y] = GetRuneForIntersects (intersects);
  54. }
  55. }
  56. return canvas;
  57. }
  58. private Rune? GetRuneForIntersects (IntersectionDefinition[] intersects)
  59. {
  60. if (!intersects.Any ())
  61. return null;
  62. // TODO: merge these intersection types to give correct rune
  63. return '.';
  64. }
  65. class IntersectionDefinition {
  66. /// <summary>
  67. /// The point at which the intersection happens
  68. /// </summary>
  69. public Point Point { get; }
  70. /// <summary>
  71. /// Defines how <see cref="Line"/> position relates
  72. /// to <see cref="Point"/>.
  73. /// </summary>
  74. public IntersectionType Type { get; }
  75. /// <summary>
  76. /// The line that intersects <see cref="Point"/>
  77. /// </summary>
  78. public StraightLine Line { get; }
  79. public IntersectionDefinition (Point point, IntersectionType type, StraightLine line)
  80. {
  81. Point = point;
  82. Type = type;
  83. Line = line;
  84. }
  85. }
  86. /// <summary>
  87. /// The type of Rune that we will use before considering
  88. /// double width, curved borders etc
  89. /// </summary>
  90. enum IntersectionRuneType
  91. {
  92. None,
  93. Dot,
  94. ULCorner,
  95. URCorner,
  96. LLCorner,
  97. LRCorner,
  98. UpperT,
  99. LowerT,
  100. RightT,
  101. LeftT,
  102. Crosshair,
  103. }
  104. enum IntersectionType {
  105. /// <summary>
  106. /// There is no intersection
  107. /// </summary>
  108. None,
  109. /// <summary>
  110. /// A line passes directly over this point traveling along
  111. /// the horizontal axis
  112. /// </summary>
  113. PassOverHorizontal,
  114. /// <summary>
  115. /// A line passes directly over this point traveling along
  116. /// the vertical axis
  117. /// </summary>
  118. PassOverVertical,
  119. /// <summary>
  120. /// A line starts at this point and is traveling up
  121. /// </summary>
  122. StartUp,
  123. /// <summary>
  124. /// A line starts at this point and is traveling right
  125. /// </summary>
  126. StartRight,
  127. /// <summary>
  128. /// A line starts at this point and is traveling down
  129. /// </summary>
  130. StartDown,
  131. /// <summary>
  132. /// A line starts at this point and is traveling left
  133. /// </summary>
  134. StartLeft,
  135. /// <summary>
  136. /// A line exists at this point who has 0 length
  137. /// </summary>
  138. Dot
  139. }
  140. class StraightLine {
  141. public Point Start { get; }
  142. public int Length { get; }
  143. public Orientation Orientation { get; }
  144. public BorderStyle Style { get; }
  145. public StraightLine (Point start, int length, Orientation orientation, BorderStyle style)
  146. {
  147. this.Start = start;
  148. this.Length = length;
  149. this.Orientation = orientation;
  150. this.Style = style;
  151. }
  152. internal IntersectionDefinition Intersects (int x, int y)
  153. {
  154. if (IsDot ()) {
  155. if (StartsAt (x, y)) {
  156. return new IntersectionDefinition (Start, IntersectionType.Dot, this);
  157. } else {
  158. return null;
  159. }
  160. }
  161. switch (Orientation) {
  162. case Orientation.Horizontal: return IntersectsHorizontally (x, y);
  163. case Orientation.Vertical: return IntersectsVertically (x, y);
  164. default: throw new ArgumentOutOfRangeException (nameof (Orientation));
  165. }
  166. }
  167. private IntersectionDefinition IntersectsHorizontally (int x, int y)
  168. {
  169. if (Start.Y != y) {
  170. return null;
  171. } else {
  172. if (StartsAt (x, y)) {
  173. return new IntersectionDefinition (
  174. Start,
  175. Length < 0 ? IntersectionType.StartLeft : IntersectionType.StartRight,
  176. this
  177. );
  178. }
  179. if (EndsAt (x, y)) {
  180. return new IntersectionDefinition (
  181. Start,
  182. Length < 0 ? IntersectionType.StartRight : IntersectionType.StartLeft,
  183. this
  184. );
  185. } else {
  186. var xmin = Math.Min (Start.X, Start.X + Length);
  187. var xmax = Math.Max (Start.X, Start.X + Length);
  188. if (xmin < x && xmax > x) {
  189. return new IntersectionDefinition (
  190. new Point (x, y),
  191. IntersectionType.PassOverHorizontal,
  192. this
  193. );
  194. }
  195. }
  196. return null;
  197. }
  198. }
  199. private IntersectionDefinition IntersectsVertically (int x, int y)
  200. {
  201. if (Start.X != x) {
  202. return null;
  203. } else {
  204. if (StartsAt (x, y)) {
  205. return new IntersectionDefinition (
  206. Start,
  207. Length < 0 ? IntersectionType.StartUp : IntersectionType.StartDown,
  208. this
  209. );
  210. }
  211. if (EndsAt (x, y)) {
  212. return new IntersectionDefinition (
  213. Start,
  214. Length < 0 ? IntersectionType.StartDown : IntersectionType.StartUp,
  215. this
  216. );
  217. } else {
  218. var ymin = Math.Min (Start.Y, Start.Y + Length);
  219. var ymax = Math.Max (Start.Y, Start.Y + Length);
  220. if (ymin < y && ymax > y) {
  221. return new IntersectionDefinition (
  222. new Point (x, y),
  223. IntersectionType.PassOverVertical,
  224. this
  225. );
  226. }
  227. }
  228. return null;
  229. }
  230. }
  231. private bool EndsAt (int x, int y)
  232. {
  233. if (Orientation == Orientation.Horizontal) {
  234. return Start.X + Length == x && Start.Y == y;
  235. }
  236. return Start.X == x && Start.Y + Length == y;
  237. }
  238. private bool StartsAt (int x, int y)
  239. {
  240. return Start.X == x && Start.Y == y;
  241. }
  242. private bool IsDot ()
  243. {
  244. return Length == 0;
  245. }
  246. }
  247. }
  248. }