LineCanvas.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. namespace Terminal.Gui {
  7. /// <summary>
  8. /// Defines the style of lines for a <see cref="LineCanvas"/>.
  9. /// </summary>
  10. public enum LineStyle {
  11. /// <summary>
  12. /// No border is drawn.
  13. /// </summary>
  14. None,
  15. /// <summary>
  16. /// The border is drawn using thin line CM.Glyphs.
  17. /// </summary>
  18. Single,
  19. /// <summary>
  20. /// The border is drawn using thin line glyphs with dashed (double and triple) straight lines.
  21. /// </summary>
  22. Dashed,
  23. /// <summary>
  24. /// The border is drawn using thin line glyphs with short dashed (triple and quadruple) straight lines.
  25. /// </summary>
  26. Dotted,
  27. /// <summary>
  28. /// The border is drawn using thin double line CM.Glyphs.
  29. /// </summary>
  30. Double,
  31. /// <summary>
  32. /// The border is drawn using heavy line CM.Glyphs.
  33. /// </summary>
  34. Heavy,
  35. /// <summary>
  36. /// The border is drawn using heavy line glyphs with dashed (double and triple) straight lines.
  37. /// </summary>
  38. HeavyDashed,
  39. /// <summary>
  40. /// The border is drawn using heavy line glyphs with short dashed (triple and quadruple) straight lines.
  41. /// </summary>
  42. HeavyDotted,
  43. /// <summary>
  44. /// The border is drawn using thin line glyphs with rounded corners.
  45. /// </summary>
  46. Rounded,
  47. /// <summary>
  48. /// The border is drawn using thin line glyphs with rounded corners and dashed (double and triple) straight lines.
  49. /// </summary>
  50. RoundedDashed,
  51. /// <summary>
  52. /// The border is drawn using thin line glyphs with rounded corners and short dashed (triple and quadruple) straight lines.
  53. /// </summary>
  54. RoundedDotted,
  55. // TODO: Support Ruler
  56. ///// <summary>
  57. ///// The border is drawn as a diagnostic ruler ("|123456789...").
  58. ///// </summary>
  59. //Ruler
  60. }
  61. /// <summary>
  62. /// Facilitates box drawing and line intersection detection
  63. /// and rendering. Does not support diagonal lines.
  64. /// </summary>
  65. public class LineCanvas {
  66. /// <summary>
  67. /// Creates a new instance.
  68. /// </summary>
  69. public LineCanvas()
  70. {
  71. ConfigurationManager.Applied += ConfigurationManager_Applied;
  72. }
  73. private void ConfigurationManager_Applied (object sender, ConfigurationManagerEventArgs e)
  74. {
  75. foreach (var irr in runeResolvers) {
  76. irr.Value.SetGlyphs ();
  77. }
  78. }
  79. private List<StraightLine> _lines = new List<StraightLine> ();
  80. Dictionary<IntersectionRuneType, IntersectionRuneResolver> runeResolvers = new Dictionary<IntersectionRuneType, IntersectionRuneResolver> {
  81. {IntersectionRuneType.ULCorner,new ULIntersectionRuneResolver()},
  82. {IntersectionRuneType.URCorner,new URIntersectionRuneResolver()},
  83. {IntersectionRuneType.LLCorner,new LLIntersectionRuneResolver()},
  84. {IntersectionRuneType.LRCorner,new LRIntersectionRuneResolver()},
  85. {IntersectionRuneType.TopTee,new TopTeeIntersectionRuneResolver()},
  86. {IntersectionRuneType.LeftTee,new LeftTeeIntersectionRuneResolver()},
  87. {IntersectionRuneType.RightTee,new RightTeeIntersectionRuneResolver()},
  88. {IntersectionRuneType.BottomTee,new BottomTeeIntersectionRuneResolver()},
  89. {IntersectionRuneType.Cross,new CrossIntersectionRuneResolver()},
  90. // TODO: Add other resolvers
  91. };
  92. /// <summary>
  93. /// <para>
  94. /// Adds a new <paramref name="length"/> long line to the canvas starting at <paramref name="start"/>.
  95. /// </para>
  96. /// <para>
  97. /// Use positive <paramref name="length"/> for the line to extend Right and negative for Left
  98. /// when <see cref="Orientation"/> is <see cref="Orientation.Horizontal"/>.
  99. /// </para>
  100. /// <para>
  101. /// Use positive <paramref name="length"/> for the line to extend Down and negative for Up
  102. /// when <see cref="Orientation"/> is <see cref="Orientation.Vertical"/>.
  103. /// </para>
  104. /// </summary>
  105. /// <param name="start">Starting point.</param>
  106. /// <param name="length">The length of line. 0 for an intersection (cross or T). Positive for Down/Right. Negative for Up/Left.</param>
  107. /// <param name="orientation">The direction of the line.</param>
  108. /// <param name="style">The style of line to use</param>
  109. /// <param name="attribute"></param>
  110. public void AddLine (Point start, int length, Orientation orientation, LineStyle style, Attribute? attribute = default)
  111. {
  112. _cachedBounds = Rect.Empty;
  113. _lines.Add (new StraightLine (start, length, orientation, style, attribute));
  114. }
  115. /// <summary>
  116. /// Adds a new line to the canvas
  117. /// </summary>
  118. /// <param name="line"></param>
  119. public void AddLine (StraightLine line)
  120. {
  121. _cachedBounds = Rect.Empty;
  122. _lines.Add (line);
  123. }
  124. /// <summary>
  125. /// Removes the last line added to the canvas
  126. /// </summary>
  127. /// <returns></returns>
  128. public StraightLine RemoveLastLine()
  129. {
  130. var l = _lines.LastOrDefault ();
  131. if(l != null) {
  132. _lines.Remove(l);
  133. }
  134. return l;
  135. }
  136. /// <summary>
  137. /// Clears all lines from the LineCanvas.
  138. /// </summary>
  139. public void Clear ()
  140. {
  141. _cachedBounds = Rect.Empty;
  142. _lines.Clear ();
  143. }
  144. /// <summary>
  145. /// Clears any cached states from the canvas
  146. /// Call this method if you make changes to lines
  147. /// that have already been added.
  148. /// </summary>
  149. public void ClearCache ()
  150. {
  151. _cachedBounds = Rect.Empty;
  152. }
  153. private Rect _cachedBounds;
  154. /// <summary>
  155. /// Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the
  156. /// line that is furthest left/top and Size is defined by the line that extends the furthest
  157. /// right/bottom.
  158. /// </summary>
  159. public Rect Bounds {
  160. get {
  161. if (_cachedBounds.IsEmpty) {
  162. if (_lines.Count == 0) {
  163. return _cachedBounds;
  164. }
  165. Rect bounds = _lines [0].Bounds;
  166. for (var i = 1; i < _lines.Count; i++) {
  167. var line = _lines [i];
  168. var lineBounds = line.Bounds;
  169. bounds = Rect.Union (bounds, lineBounds);
  170. }
  171. if (bounds.Width == 0) {
  172. bounds.Width = 1;
  173. }
  174. if (bounds.Height == 0) {
  175. bounds.Height = 1;
  176. }
  177. _cachedBounds = new Rect (bounds.X, bounds.Y, bounds.Width, bounds.Height);
  178. }
  179. return _cachedBounds;
  180. }
  181. }
  182. // TODO: Unless there's an obvious use case for this API we should delete it in favor of the
  183. // simpler version that doensn't take an area.
  184. /// <summary>
  185. /// Evaluates the lines that have been added to the canvas and returns a map containing
  186. /// the glyphs and their locations. The glyphs are the characters that should be rendered
  187. /// so that all lines connect up with the appropriate intersection symbols.
  188. /// </summary>
  189. /// <param name="inArea">A rectangle to constrain the search by.</param>
  190. /// <returns>A map of the points within the canvas that intersect with <paramref name="inArea"/>.</returns>
  191. public Dictionary<Point, Rune> GetMap (Rect inArea)
  192. {
  193. var map = new Dictionary<Point, Rune> ();
  194. // walk through each pixel of the bitmap
  195. for (int y = inArea.Y; y < inArea.Y + inArea.Height; y++) {
  196. for (int x = inArea.X; x < inArea.X + inArea.Width; x++) {
  197. var intersects = _lines
  198. .Select (l => l.Intersects (x, y))
  199. .Where (i => i != null)
  200. .ToArray ();
  201. var rune = GetRuneForIntersects (Application.Driver, intersects);
  202. if (rune != null) {
  203. map.Add (new Point (x, y), rune.Value);
  204. }
  205. }
  206. }
  207. return map;
  208. }
  209. /// <summary>
  210. /// Evaluates the lines that have been added to the canvas and returns a map containing
  211. /// the glyphs and their locations. The glyphs are the characters that should be rendered
  212. /// so that all lines connect up with the appropriate intersection symbols.
  213. /// </summary>
  214. /// <returns>A map of all the points within the canvas.</returns>
  215. public Dictionary<Point, Cell> GetCellMap ()
  216. {
  217. var map = new Dictionary<Point, Cell> ();
  218. // walk through each pixel of the bitmap
  219. for (int y = Bounds.Y; y < Bounds.Y + Bounds.Height; y++) {
  220. for (int x = Bounds.X; x < Bounds.X + Bounds.Width; x++) {
  221. var intersects = _lines
  222. .Select (l => l.Intersects (x, y))
  223. .Where (i => i != null)
  224. .ToArray ();
  225. var cell = GetCellForIntersects (Application.Driver, intersects);
  226. if (cell != null) {
  227. map.Add (new Point (x, y), cell);
  228. }
  229. }
  230. }
  231. return map;
  232. }
  233. /// <summary>
  234. /// Evaluates the lines that have been added to the canvas and returns a map containing
  235. /// the glyphs and their locations. The glyphs are the characters that should be rendered
  236. /// so that all lines connect up with the appropriate intersection symbols.
  237. /// </summary>
  238. /// <returns>A map of all the points within the canvas.</returns>
  239. public Dictionary<Point, Rune> GetMap () => GetMap (Bounds);
  240. /// <summary>
  241. /// Returns the contents of the line canvas rendered to a string. The string
  242. /// will include all columns and rows, even if <see cref="Bounds"/> has negative coordinates.
  243. /// For example, if the canvas contains a single line that starts at (-1,-1) with a length of 2, the
  244. /// rendered string will have a length of 2.
  245. /// </summary>
  246. /// <returns>The canvas rendered to a string.</returns>
  247. public override string ToString ()
  248. {
  249. if (Bounds.IsEmpty) {
  250. return string.Empty;
  251. }
  252. // Generate the rune map for the entire canvas
  253. var runeMap = GetMap ();
  254. // Create the rune canvas
  255. Rune [,] canvas = new Rune [Bounds.Height, Bounds.Width];
  256. // Copy the rune map to the canvas, adjusting for any negative coordinates
  257. foreach (var kvp in runeMap) {
  258. int x = kvp.Key.X - Bounds.X;
  259. int y = kvp.Key.Y - Bounds.Y;
  260. canvas [y, x] = kvp.Value;
  261. }
  262. // Convert the canvas to a string
  263. StringBuilder sb = new StringBuilder ();
  264. for (int y = 0; y < canvas.GetLength (0); y++) {
  265. for (int x = 0; x < canvas.GetLength (1); x++) {
  266. Rune r = canvas [y, x];
  267. sb.Append (r.Value == 0 ? ' ' : r.ToString ());
  268. }
  269. if (y < canvas.GetLength (0) - 1) {
  270. sb.AppendLine ();
  271. }
  272. }
  273. return sb.ToString ();
  274. }
  275. private abstract class IntersectionRuneResolver {
  276. internal Rune _round;
  277. internal Rune _doubleH;
  278. internal Rune _doubleV;
  279. internal Rune _doubleBoth;
  280. internal Rune _thickH;
  281. internal Rune _thickV;
  282. internal Rune _thickBoth;
  283. internal Rune _normal;
  284. public IntersectionRuneResolver()
  285. {
  286. SetGlyphs ();
  287. }
  288. /// <summary>
  289. /// Sets the glyphs used. Call this method after construction and any time
  290. /// ConfigurationManager has updated the settings.
  291. /// </summary>
  292. public abstract void SetGlyphs ();
  293. public Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects)
  294. {
  295. var useRounded = intersects.Any (i => i.Line.Length != 0 && (
  296. i.Line.Style == LineStyle.Rounded || i.Line.Style == LineStyle.RoundedDashed || i.Line.Style == LineStyle.RoundedDotted));
  297. // Note that there aren't any glyphs for intersections of double lines with heavy lines
  298. bool doubleHorizontal = intersects.Any (l => l.Line.Orientation == Orientation.Horizontal && l.Line.Style == LineStyle.Double);
  299. bool doubleVertical = intersects.Any (l => l.Line.Orientation == Orientation.Vertical && l.Line.Style == LineStyle.Double);
  300. bool thickHorizontal = intersects.Any (l => l.Line.Orientation == Orientation.Horizontal && (
  301. l.Line.Style == LineStyle.Heavy || l.Line.Style == LineStyle.HeavyDashed || l.Line.Style == LineStyle.HeavyDotted));
  302. bool thickVertical = intersects.Any (l => l.Line.Orientation == Orientation.Vertical && (
  303. l.Line.Style == LineStyle.Heavy || l.Line.Style == LineStyle.HeavyDashed || l.Line.Style == LineStyle.HeavyDotted));
  304. if (doubleHorizontal) {
  305. return doubleVertical ? _doubleBoth : _doubleH;
  306. }
  307. if (doubleVertical) {
  308. return _doubleV;
  309. }
  310. if (thickHorizontal) {
  311. return thickVertical ? _thickBoth : _thickH;
  312. }
  313. if (thickVertical) {
  314. return _thickV;
  315. }
  316. return useRounded ? _round : _normal;
  317. }
  318. }
  319. private class ULIntersectionRuneResolver : IntersectionRuneResolver {
  320. public override void SetGlyphs ()
  321. {
  322. _round = CM.Glyphs.ULCornerR;
  323. _doubleH = CM.Glyphs.ULCornerSingleDbl;
  324. _doubleV = CM.Glyphs.ULCornerDblSingle;
  325. _doubleBoth = CM.Glyphs.ULCornerDbl;
  326. _thickH = CM.Glyphs.ULCornerLtHv;
  327. _thickV = CM.Glyphs.ULCornerHvLt;
  328. _thickBoth = CM.Glyphs.ULCornerHv;
  329. _normal = CM.Glyphs.ULCorner;
  330. }
  331. }
  332. private class URIntersectionRuneResolver : IntersectionRuneResolver {
  333. public override void SetGlyphs ()
  334. {
  335. _round = CM.Glyphs.URCornerR;
  336. _doubleH = CM.Glyphs.URCornerSingleDbl;
  337. _doubleV = CM.Glyphs.URCornerDblSingle;
  338. _doubleBoth = CM.Glyphs.URCornerDbl;
  339. _thickH = CM.Glyphs.URCornerHvLt;
  340. _thickV = CM.Glyphs.URCornerLtHv;
  341. _thickBoth = CM.Glyphs.URCornerHv;
  342. _normal = CM.Glyphs.URCorner;
  343. }
  344. }
  345. private class LLIntersectionRuneResolver : IntersectionRuneResolver {
  346. public override void SetGlyphs ()
  347. {
  348. _round = CM.Glyphs.LLCornerR;
  349. _doubleH = CM.Glyphs.LLCornerSingleDbl;
  350. _doubleV = CM.Glyphs.LLCornerDblSingle;
  351. _doubleBoth = CM.Glyphs.LLCornerDbl;
  352. _thickH = CM.Glyphs.LLCornerLtHv;
  353. _thickV = CM.Glyphs.LLCornerHvLt;
  354. _thickBoth = CM.Glyphs.LLCornerHv;
  355. _normal = CM.Glyphs.LLCorner;
  356. }
  357. }
  358. private class LRIntersectionRuneResolver : IntersectionRuneResolver {
  359. public override void SetGlyphs ()
  360. {
  361. _round = CM.Glyphs.LRCornerR;
  362. _doubleH = CM.Glyphs.LRCornerSingleDbl;
  363. _doubleV = CM.Glyphs.LRCornerDblSingle;
  364. _doubleBoth = CM.Glyphs.LRCornerDbl;
  365. _thickH = CM.Glyphs.LRCornerLtHv;
  366. _thickV = CM.Glyphs.LRCornerHvLt;
  367. _thickBoth = CM.Glyphs.LRCornerHv;
  368. _normal = CM.Glyphs.LRCorner;
  369. }
  370. }
  371. private class TopTeeIntersectionRuneResolver : IntersectionRuneResolver {
  372. public override void SetGlyphs ()
  373. {
  374. _round = CM.Glyphs.TopTee;
  375. _doubleH = CM.Glyphs.TopTeeDblH;
  376. _doubleV = CM.Glyphs.TopTeeDblV;
  377. _doubleBoth = CM.Glyphs.TopTeeDbl;
  378. _thickH = CM.Glyphs.TopTeeHvH;
  379. _thickV = CM.Glyphs.TopTeeHvV;
  380. _thickBoth = CM.Glyphs.TopTeeHvDblH;
  381. _normal = CM.Glyphs.TopTee;
  382. }
  383. }
  384. private class LeftTeeIntersectionRuneResolver : IntersectionRuneResolver {
  385. public override void SetGlyphs ()
  386. {
  387. _round = CM.Glyphs.LeftTee;
  388. _doubleH = CM.Glyphs.LeftTeeDblH;
  389. _doubleV = CM.Glyphs.LeftTeeDblV;
  390. _doubleBoth = CM.Glyphs.LeftTeeDbl;
  391. _thickH = CM.Glyphs.LeftTeeHvH;
  392. _thickV = CM.Glyphs.LeftTeeHvV;
  393. _thickBoth = CM.Glyphs.LeftTeeHvDblH;
  394. _normal = CM.Glyphs.LeftTee;
  395. }
  396. }
  397. private class RightTeeIntersectionRuneResolver : IntersectionRuneResolver {
  398. public override void SetGlyphs ()
  399. {
  400. _round = CM.Glyphs.RightTee;
  401. _doubleH = CM.Glyphs.RightTeeDblH;
  402. _doubleV = CM.Glyphs.RightTeeDblV;
  403. _doubleBoth = CM.Glyphs.RightTeeDbl;
  404. _thickH = CM.Glyphs.RightTeeHvH;
  405. _thickV = CM.Glyphs.RightTeeHvV;
  406. _thickBoth = CM.Glyphs.RightTeeHvDblH;
  407. _normal = CM.Glyphs.RightTee;
  408. }
  409. }
  410. private class BottomTeeIntersectionRuneResolver : IntersectionRuneResolver {
  411. public override void SetGlyphs ()
  412. {
  413. _round = CM.Glyphs.BottomTee;
  414. _doubleH = CM.Glyphs.BottomTeeDblH;
  415. _doubleV = CM.Glyphs.BottomTeeDblV;
  416. _doubleBoth = CM.Glyphs.BottomTeeDbl;
  417. _thickH = CM.Glyphs.BottomTeeHvH;
  418. _thickV = CM.Glyphs.BottomTeeHvV;
  419. _thickBoth = CM.Glyphs.BottomTeeHvDblH;
  420. _normal = CM.Glyphs.BottomTee;
  421. }
  422. }
  423. private class CrossIntersectionRuneResolver : IntersectionRuneResolver {
  424. public override void SetGlyphs ()
  425. {
  426. _round = CM.Glyphs.Cross;
  427. _doubleH = CM.Glyphs.CrossDblH;
  428. _doubleV = CM.Glyphs.CrossDblV;
  429. _doubleBoth = CM.Glyphs.CrossDbl;
  430. _thickH = CM.Glyphs.CrossHvH;
  431. _thickV = CM.Glyphs.CrossHvV;
  432. _thickBoth = CM.Glyphs.CrossHv;
  433. _normal = CM.Glyphs.Cross;
  434. }
  435. }
  436. private Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects)
  437. {
  438. if (!intersects.Any ()) {
  439. return null;
  440. }
  441. var runeType = GetRuneTypeForIntersects (intersects);
  442. if (runeResolvers.ContainsKey (runeType)) {
  443. return runeResolvers [runeType].GetRuneForIntersects (driver, intersects);
  444. }
  445. // TODO: Remove these once we have all of the below ported to IntersectionRuneResolvers
  446. var useDouble = intersects.Any (i => i.Line.Style == LineStyle.Double);
  447. var useDashed = intersects.Any (i => i.Line.Style == LineStyle.Dashed || i.Line.Style == LineStyle.RoundedDashed);
  448. var useDotted = intersects.Any (i => i.Line.Style == LineStyle.Dotted || i.Line.Style == LineStyle.RoundedDotted);
  449. // horiz and vert lines same as Single for Rounded
  450. var useThick = intersects.Any (i => i.Line.Style == LineStyle.Heavy);
  451. var useThickDashed = intersects.Any (i => i.Line.Style == LineStyle.HeavyDashed);
  452. var useThickDotted = intersects.Any (i => i.Line.Style == LineStyle.HeavyDotted);
  453. // TODO: Support ruler
  454. //var useRuler = intersects.Any (i => i.Line.Style == LineStyle.Ruler && i.Line.Length != 0);
  455. // TODO: maybe make these resolvers too for simplicity?
  456. switch (runeType) {
  457. case IntersectionRuneType.None:
  458. return null;
  459. case IntersectionRuneType.Dot:
  460. return (Rune)CM.Glyphs.Dot;
  461. case IntersectionRuneType.HLine:
  462. if (useDouble) {
  463. return CM.Glyphs.HLineDbl;
  464. }
  465. if (useDashed) {
  466. return CM.Glyphs.HLineDa2;
  467. }
  468. if (useDotted) {
  469. return CM.Glyphs.HLineDa3;
  470. }
  471. return useThick ? CM.Glyphs.HLineHv : (useThickDashed ? CM.Glyphs.HLineHvDa2 : (useThickDotted ? CM.Glyphs.HLineHvDa3 : CM.Glyphs.HLine));
  472. case IntersectionRuneType.VLine:
  473. if (useDouble) {
  474. return CM.Glyphs.VLineDbl;
  475. }
  476. if (useDashed) {
  477. return CM.Glyphs.VLineDa3;
  478. }
  479. if (useDotted) {
  480. return CM.Glyphs.VLineDa4;
  481. }
  482. return useThick ? CM.Glyphs.VLineHv : (useThickDashed ? CM.Glyphs.VLineHvDa3 : (useThickDotted ? CM.Glyphs.VLineHvDa4 : CM.Glyphs.VLine));
  483. default: throw new Exception ("Could not find resolver or switch case for " + nameof (runeType) + ":" + runeType);
  484. }
  485. }
  486. private Attribute? GetAttributeForIntersects (IntersectionDefinition [] intersects)
  487. {
  488. var set = new List<IntersectionDefinition> (intersects.Where (i => i.Line.Attribute?.HasValidColors ?? false));
  489. if (set.Count == 0) {
  490. return null;
  491. }
  492. return set [0].Line.Attribute;
  493. }
  494. private Cell GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects)
  495. {
  496. if (!intersects.Any ()) {
  497. return null;
  498. }
  499. var cell = new Cell ();
  500. cell.Rune = GetRuneForIntersects (driver, intersects);
  501. cell.Attribute = GetAttributeForIntersects (intersects);
  502. return cell;
  503. }
  504. private IntersectionRuneType GetRuneTypeForIntersects (IntersectionDefinition [] intersects)
  505. {
  506. var set = new HashSet<IntersectionType> (intersects.Select (i => i.Type));
  507. #region Cross Conditions
  508. if (Has (set,
  509. IntersectionType.PassOverHorizontal,
  510. IntersectionType.PassOverVertical
  511. )) {
  512. return IntersectionRuneType.Cross;
  513. }
  514. if (Has (set,
  515. IntersectionType.PassOverVertical,
  516. IntersectionType.StartLeft,
  517. IntersectionType.StartRight
  518. )) {
  519. return IntersectionRuneType.Cross;
  520. }
  521. if (Has (set,
  522. IntersectionType.PassOverHorizontal,
  523. IntersectionType.StartUp,
  524. IntersectionType.StartDown
  525. )) {
  526. return IntersectionRuneType.Cross;
  527. }
  528. if (Has (set,
  529. IntersectionType.StartLeft,
  530. IntersectionType.StartRight,
  531. IntersectionType.StartUp,
  532. IntersectionType.StartDown)) {
  533. return IntersectionRuneType.Cross;
  534. }
  535. #endregion
  536. #region Corner Conditions
  537. if (Exactly (set,
  538. IntersectionType.StartRight,
  539. IntersectionType.StartDown)) {
  540. return IntersectionRuneType.ULCorner;
  541. }
  542. if (Exactly (set,
  543. IntersectionType.StartLeft,
  544. IntersectionType.StartDown)) {
  545. return IntersectionRuneType.URCorner;
  546. }
  547. if (Exactly (set,
  548. IntersectionType.StartUp,
  549. IntersectionType.StartLeft)) {
  550. return IntersectionRuneType.LRCorner;
  551. }
  552. if (Exactly (set,
  553. IntersectionType.StartUp,
  554. IntersectionType.StartRight)) {
  555. return IntersectionRuneType.LLCorner;
  556. }
  557. #endregion Corner Conditions
  558. #region T Conditions
  559. if (Has (set,
  560. IntersectionType.PassOverHorizontal,
  561. IntersectionType.StartDown)) {
  562. return IntersectionRuneType.TopTee;
  563. }
  564. if (Has (set,
  565. IntersectionType.StartRight,
  566. IntersectionType.StartLeft,
  567. IntersectionType.StartDown)) {
  568. return IntersectionRuneType.TopTee;
  569. }
  570. if (Has (set,
  571. IntersectionType.PassOverHorizontal,
  572. IntersectionType.StartUp)) {
  573. return IntersectionRuneType.BottomTee;
  574. }
  575. if (Has (set,
  576. IntersectionType.StartRight,
  577. IntersectionType.StartLeft,
  578. IntersectionType.StartUp)) {
  579. return IntersectionRuneType.BottomTee;
  580. }
  581. if (Has (set,
  582. IntersectionType.PassOverVertical,
  583. IntersectionType.StartRight)) {
  584. return IntersectionRuneType.LeftTee;
  585. }
  586. if (Has (set,
  587. IntersectionType.StartRight,
  588. IntersectionType.StartDown,
  589. IntersectionType.StartUp)) {
  590. return IntersectionRuneType.LeftTee;
  591. }
  592. if (Has (set,
  593. IntersectionType.PassOverVertical,
  594. IntersectionType.StartLeft)) {
  595. return IntersectionRuneType.RightTee;
  596. }
  597. if (Has (set,
  598. IntersectionType.StartLeft,
  599. IntersectionType.StartDown,
  600. IntersectionType.StartUp)) {
  601. return IntersectionRuneType.RightTee;
  602. }
  603. #endregion
  604. if (All (intersects, Orientation.Horizontal)) {
  605. return IntersectionRuneType.HLine;
  606. }
  607. if (All (intersects, Orientation.Vertical)) {
  608. return IntersectionRuneType.VLine;
  609. }
  610. return IntersectionRuneType.Dot;
  611. }
  612. private bool All (IntersectionDefinition [] intersects, Orientation orientation)
  613. {
  614. return intersects.All (i => i.Line.Orientation == orientation);
  615. }
  616. /// <summary>
  617. /// Returns true if the <paramref name="intersects"/> collection has all the <paramref name="types"/>
  618. /// specified (i.e. AND).
  619. /// </summary>
  620. /// <param name="intersects"></param>
  621. /// <param name="types"></param>
  622. /// <returns></returns>
  623. private bool Has (HashSet<IntersectionType> intersects, params IntersectionType [] types)
  624. {
  625. return types.All (t => intersects.Contains (t));
  626. }
  627. /// <summary>
  628. /// Returns true if all requested <paramref name="types"/> appear in <paramref name="intersects"/>
  629. /// and there are no additional <see cref="IntersectionRuneType"/>
  630. /// </summary>
  631. /// <param name="intersects"></param>
  632. /// <param name="types"></param>
  633. /// <returns></returns>
  634. private bool Exactly (HashSet<IntersectionType> intersects, params IntersectionType [] types)
  635. {
  636. return intersects.SetEquals (types);
  637. }
  638. /// <summary>
  639. /// Merges one line canvas into this one.
  640. /// </summary>
  641. /// <param name="lineCanvas"></param>
  642. public void Merge (LineCanvas lineCanvas)
  643. {
  644. foreach (var line in lineCanvas._lines) {
  645. AddLine (line);
  646. }
  647. }
  648. }
  649. internal class IntersectionDefinition {
  650. /// <summary>
  651. /// The point at which the intersection happens
  652. /// </summary>
  653. internal Point Point { get; }
  654. /// <summary>
  655. /// Defines how <see cref="Line"/> position relates
  656. /// to <see cref="Point"/>.
  657. /// </summary>
  658. internal IntersectionType Type { get; }
  659. /// <summary>
  660. /// The line that intersects <see cref="Point"/>
  661. /// </summary>
  662. internal StraightLine Line { get; }
  663. internal IntersectionDefinition (Point point, IntersectionType type, StraightLine line)
  664. {
  665. Point = point;
  666. Type = type;
  667. Line = line;
  668. }
  669. }
  670. /// <summary>
  671. /// The type of Rune that we will use before considering
  672. /// double width, curved borders etc
  673. /// </summary>
  674. internal enum IntersectionRuneType {
  675. None,
  676. Dot,
  677. ULCorner,
  678. URCorner,
  679. LLCorner,
  680. LRCorner,
  681. TopTee,
  682. BottomTee,
  683. RightTee,
  684. LeftTee,
  685. Cross,
  686. HLine,
  687. VLine,
  688. }
  689. internal enum IntersectionType {
  690. /// <summary>
  691. /// There is no intersection
  692. /// </summary>
  693. None,
  694. /// <summary>
  695. /// A line passes directly over this point traveling along
  696. /// the horizontal axis
  697. /// </summary>
  698. PassOverHorizontal,
  699. /// <summary>
  700. /// A line passes directly over this point traveling along
  701. /// the vertical axis
  702. /// </summary>
  703. PassOverVertical,
  704. /// <summary>
  705. /// A line starts at this point and is traveling up
  706. /// </summary>
  707. StartUp,
  708. /// <summary>
  709. /// A line starts at this point and is traveling right
  710. /// </summary>
  711. StartRight,
  712. /// <summary>
  713. /// A line starts at this point and is traveling down
  714. /// </summary>
  715. StartDown,
  716. /// <summary>
  717. /// A line starts at this point and is traveling left
  718. /// </summary>
  719. StartLeft,
  720. /// <summary>
  721. /// A line exists at this point who has 0 length
  722. /// </summary>
  723. Dot
  724. }
  725. }