LineCanvas.cs 31 KB

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