StandardTextRenderer.cs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. namespace Terminal.Gui.Text;
  6. /// <summary>
  7. /// Standard implementation of <see cref="ITextRenderer"/> that renders formatted text to the console.
  8. /// </summary>
  9. public class StandardTextRenderer : ITextRenderer
  10. {
  11. /// <inheritdoc />
  12. public void Draw(
  13. FormattedText formattedText,
  14. Rectangle screen,
  15. Attribute normalColor,
  16. Attribute hotColor,
  17. bool fillRemaining = false,
  18. Rectangle maximum = default,
  19. IConsoleDriver? driver = null)
  20. {
  21. if (driver is null)
  22. {
  23. driver = Application.Driver;
  24. }
  25. if (driver is null || formattedText.Lines.Count == 0)
  26. {
  27. return;
  28. }
  29. driver.SetAttribute(normalColor);
  30. // Calculate effective drawing area
  31. Rectangle maxScreen = CalculateMaxScreen(screen, maximum);
  32. if (maxScreen.Width == 0 || maxScreen.Height == 0)
  33. {
  34. return;
  35. }
  36. // TODO: Implement alignment using the Aligner engine instead of custom logic
  37. // For now, use simplified alignment
  38. int startY = screen.Y;
  39. int lineIndex = 0;
  40. foreach (var line in formattedText.Lines)
  41. {
  42. if (lineIndex >= maxScreen.Height)
  43. {
  44. break;
  45. }
  46. int y = startY + lineIndex;
  47. if (y >= maxScreen.Bottom || y < maxScreen.Top)
  48. {
  49. lineIndex++;
  50. continue;
  51. }
  52. int x = screen.X;
  53. // Draw each run in the line
  54. foreach (var run in line.Runs)
  55. {
  56. if (string.IsNullOrEmpty(run.Text))
  57. {
  58. continue;
  59. }
  60. // Set appropriate color
  61. driver.SetAttribute(run.IsHotKey ? hotColor : normalColor);
  62. // Draw the run text
  63. driver.Move(x, y);
  64. foreach (var rune in run.Text.EnumerateRunes())
  65. {
  66. if (x >= maxScreen.Right)
  67. {
  68. break;
  69. }
  70. if (x >= maxScreen.Left)
  71. {
  72. driver.AddRune(rune);
  73. }
  74. x += Math.Max(rune.GetColumns(), 1);
  75. }
  76. }
  77. // Fill remaining space if requested
  78. if (fillRemaining && x < maxScreen.Right)
  79. {
  80. driver.SetAttribute(normalColor);
  81. while (x < maxScreen.Right)
  82. {
  83. driver.Move(x, y);
  84. driver.AddRune(' ');
  85. x++;
  86. }
  87. }
  88. lineIndex++;
  89. }
  90. }
  91. /// <inheritdoc />
  92. public Region GetDrawRegion(
  93. FormattedText formattedText,
  94. Rectangle screen,
  95. Rectangle maximum = default)
  96. {
  97. var region = new Region();
  98. if (formattedText.Lines.Count == 0)
  99. {
  100. return region;
  101. }
  102. Rectangle maxScreen = CalculateMaxScreen(screen, maximum);
  103. if (maxScreen.Width == 0 || maxScreen.Height == 0)
  104. {
  105. return region;
  106. }
  107. int startY = screen.Y;
  108. int lineIndex = 0;
  109. foreach (var line in formattedText.Lines)
  110. {
  111. if (lineIndex >= maxScreen.Height)
  112. {
  113. break;
  114. }
  115. int y = startY + lineIndex;
  116. if (y >= maxScreen.Bottom || y < maxScreen.Top)
  117. {
  118. lineIndex++;
  119. continue;
  120. }
  121. int x = screen.X;
  122. int lineWidth = 0;
  123. // Calculate total width of the line
  124. foreach (var run in line.Runs)
  125. {
  126. if (!string.IsNullOrEmpty(run.Text))
  127. {
  128. lineWidth += run.Text.GetColumns();
  129. }
  130. }
  131. if (lineWidth > 0 && x < maxScreen.Right)
  132. {
  133. int rightBound = Math.Min(x + lineWidth, maxScreen.Right);
  134. region.Union(new Rectangle(x, y, rightBound - x, 1));
  135. }
  136. lineIndex++;
  137. }
  138. return region;
  139. }
  140. private static Rectangle CalculateMaxScreen(Rectangle screen, Rectangle maximum)
  141. {
  142. if (maximum == default)
  143. {
  144. return screen;
  145. }
  146. return new Rectangle(
  147. Math.Max(maximum.X, screen.X),
  148. Math.Max(maximum.Y, screen.Y),
  149. Math.Max(Math.Min(maximum.Width, maximum.Right - screen.Left), 0),
  150. Math.Max(Math.Min(maximum.Height, maximum.Bottom - screen.Top), 0)
  151. );
  152. }
  153. }