TextView.Layout.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. namespace Terminal.Gui.Views;
  2. /// <summary>Viewport, scrolling, and content area management methods for TextView</summary>
  3. public partial class TextView
  4. {
  5. /// <summary>
  6. /// Configures the ScrollBars to work with the modern View scrolling system.
  7. /// </summary>
  8. private void ConfigureLayout ()
  9. {
  10. // Subscribe to ViewportChanged to sync internal scroll fields
  11. ViewportChanged += TextView_ViewportChanged;
  12. // Vertical ScrollBar: AutoShow enabled by default as per requirements
  13. VerticalScrollBar.AutoShow = true;
  14. // Horizontal ScrollBar: AutoShow tracks WordWrap as per requirements
  15. HorizontalScrollBar.AutoShow = !WordWrap;
  16. }
  17. private void TextView_LayoutComplete (object? sender, LayoutEventArgs e)
  18. {
  19. WrapTextModel ();
  20. UpdateContentSize ();
  21. AdjustScrollPosition ();
  22. }
  23. private void TextView_ViewportChanged (object? sender, DrawEventArgs e)
  24. {
  25. // Sync internal scroll position fields with Viewport
  26. // Only update if values actually changed to prevent infinite loops
  27. if (_topRow != Viewport.Y)
  28. {
  29. _topRow = Viewport.Y;
  30. }
  31. if (_leftColumn != Viewport.X)
  32. {
  33. _leftColumn = Viewport.X;
  34. }
  35. }
  36. /// <summary>
  37. /// INTERNAL: Adjusts the scroll position and cursor to ensure the cursor is visible in the viewport.
  38. /// This method handles both horizontal and vertical scrolling, word wrap considerations, and syncs
  39. /// the internal scroll fields with the Viewport property.
  40. /// </summary>
  41. private void AdjustScrollPosition ()
  42. {
  43. (int width, int height) offB = GetViewportClipping ();
  44. List<Cell> line = GetCurrentLine ();
  45. bool need = NeedsDraw || _wrapNeeded || !Used;
  46. (int size, int length) tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth);
  47. (int size, int length) dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true, TabWidth);
  48. if (!_wordWrap && CurrentColumn < _leftColumn)
  49. {
  50. _leftColumn = CurrentColumn;
  51. need = true;
  52. }
  53. else if (!_wordWrap
  54. && (CurrentColumn - _leftColumn + 1 > Viewport.Width + offB.width || dSize.size + 1 >= Viewport.Width + offB.width))
  55. {
  56. _leftColumn = TextModel.CalculateLeftColumn (
  57. line,
  58. _leftColumn,
  59. CurrentColumn,
  60. Viewport.Width + offB.width,
  61. TabWidth
  62. );
  63. need = true;
  64. }
  65. else if ((_wordWrap && _leftColumn > 0) || (dSize.size < Viewport.Width + offB.width && tSize.size < Viewport.Width + offB.width))
  66. {
  67. if (_leftColumn > 0)
  68. {
  69. _leftColumn = 0;
  70. need = true;
  71. }
  72. }
  73. if (CurrentRow < _topRow)
  74. {
  75. _topRow = CurrentRow;
  76. need = true;
  77. }
  78. else if (CurrentRow - _topRow >= Viewport.Height + offB.height)
  79. {
  80. _topRow = Math.Min (Math.Max (CurrentRow - Viewport.Height + 1, 0), CurrentRow);
  81. need = true;
  82. }
  83. else if (_topRow > 0 && CurrentRow < _topRow)
  84. {
  85. _topRow = Math.Max (_topRow - 1, 0);
  86. need = true;
  87. }
  88. // Sync Viewport with the internal scroll position
  89. if (IsInitialized && (_leftColumn != Viewport.X || _topRow != Viewport.Y))
  90. {
  91. Viewport = new Rectangle (_leftColumn, _topRow, Viewport.Width, Viewport.Height);
  92. }
  93. if (need)
  94. {
  95. if (_wrapNeeded)
  96. {
  97. WrapTextModel ();
  98. _wrapNeeded = false;
  99. }
  100. SetNeedsDraw ();
  101. }
  102. else
  103. {
  104. if (IsInitialized)
  105. {
  106. PositionCursor ();
  107. }
  108. }
  109. OnUnwrappedCursorPosition ();
  110. }
  111. /// <summary>
  112. /// INTERNAL: Calculates the viewport clipping caused by the view extending beyond the SuperView's boundaries.
  113. /// Returns negative width and height offsets when the viewport extends beyond the SuperView, representing
  114. /// how much of the viewport is clipped.
  115. /// </summary>
  116. /// <returns>A tuple containing the width and height clipping offsets (negative when clipped).</returns>
  117. private (int width, int height) GetViewportClipping ()
  118. {
  119. var w = 0;
  120. var h = 0;
  121. if (SuperView?.Viewport.Right - Viewport.Right < 0)
  122. {
  123. w = SuperView!.Viewport.Right - Viewport.Right - 1;
  124. }
  125. if (SuperView?.Viewport.Bottom - Viewport.Bottom < 0)
  126. {
  127. h = SuperView!.Viewport.Bottom - Viewport.Bottom - 1;
  128. }
  129. return (w, h);
  130. }
  131. /// <summary>
  132. /// INTERNAL: Updates the content size based on the text model dimensions.
  133. /// When word wrap is enabled, content width equals viewport width.
  134. /// Otherwise, calculates the maximum line width from the entire text model.
  135. /// Content height is always the number of lines in the model.
  136. /// </summary>
  137. private void UpdateContentSize ()
  138. {
  139. int contentHeight = Math.Max (_model.Count, 1);
  140. // For horizontal size: if word wrap is enabled, content width equals viewport width
  141. // Otherwise, calculate the maximum line width from the entire text model
  142. int contentWidth;
  143. if (_wordWrap)
  144. {
  145. // Word wrap: content width follows viewport width
  146. contentWidth = Math.Max (Viewport.Width, 1);
  147. }
  148. else
  149. {
  150. // No word wrap: calculate max line width
  151. // Cache the current value to avoid recalculating on every call
  152. contentWidth = Math.Max (_model.GetMaxVisibleLine (0, _model.Count, TabWidth), 1);
  153. }
  154. SetContentSize (new Size (contentWidth, contentHeight));
  155. }
  156. }