TextView.Layout.cs 5.6 KB

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