View.NeedsDraw.cs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. namespace Terminal.Gui.ViewBase;
  2. public partial class View
  3. {
  4. // NOTE: NeedsDrawRect is not currently used to clip drawing to only the invalidated region.
  5. // It is only used within SetNeedsDraw to propagate redraw requests to subviews.
  6. // NOTE: Consider changing NeedsDrawRect from Rectangle to Region for more precise invalidation
  7. // NeedsDraw is already efficiently cached via NeedsDrawRect. It checks:
  8. // 1. NeedsDrawRect (cached by SetNeedsDraw/ClearNeedsDraw)
  9. // 2. Adornment NeedsDraw flags (each cached separately)
  10. /// <summary>
  11. /// INTERNAL: Gets the viewport-relative region that needs to be redrawn.
  12. /// </summary>
  13. internal Rectangle NeedsDrawRect { get; private set; } = Rectangle.Empty;
  14. /// <summary>Gets whether the view needs to be redrawn.</summary>
  15. /// <remarks>
  16. /// <para>
  17. /// Will be <see langword="true"/> if the <see cref="NeedsLayout"/> property is <see langword="true"/> or if
  18. /// any part of the view's <see cref="Viewport"/> needs to be redrawn.
  19. /// </para>
  20. /// <para>
  21. /// Setting has no effect on <see cref="NeedsLayout"/>.
  22. /// </para>
  23. /// </remarks>
  24. public bool NeedsDraw => Visible && (NeedsDrawRect != Rectangle.Empty || Margin?.NeedsDraw == true || Border?.NeedsDraw == true || Padding?.NeedsDraw == true);
  25. /// <summary>Sets <see cref="NeedsDraw"/> to <see langword="true"/> indicating the <see cref="Viewport"/> of this View needs to be redrawn.</summary>
  26. /// <remarks>
  27. /// If the view is not visible (<see cref="Visible"/> is <see langword="false"/>), this method
  28. /// does nothing.
  29. /// </remarks>
  30. public void SetNeedsDraw ()
  31. {
  32. Rectangle viewport = Viewport;
  33. if (!Visible || (NeedsDrawRect != Rectangle.Empty && viewport.IsEmpty))
  34. {
  35. // This handles the case where the view has not been initialized yet
  36. return;
  37. }
  38. SetNeedsDraw (viewport);
  39. }
  40. /// <summary>Expands the area of this view needing to be redrawn to include <paramref name="viewPortRelativeRegion"/>.</summary>
  41. /// <remarks>
  42. /// <para>
  43. /// The location of <paramref name="viewPortRelativeRegion"/> is relative to the View's <see cref="Viewport"/>.
  44. /// </para>
  45. /// <para>
  46. /// If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), the area to be
  47. /// redrawn will be the <paramref name="viewPortRelativeRegion"/>.
  48. /// </para>
  49. /// </remarks>
  50. /// <param name="viewPortRelativeRegion">The <see cref="Viewport"/>relative region that needs to be redrawn.</param>
  51. public void SetNeedsDraw (Rectangle viewPortRelativeRegion)
  52. {
  53. if (!Visible)
  54. {
  55. return;
  56. }
  57. if (NeedsDrawRect.IsEmpty)
  58. {
  59. NeedsDrawRect = viewPortRelativeRegion;
  60. }
  61. else
  62. {
  63. int x = Math.Min (Viewport.X, viewPortRelativeRegion.X);
  64. int y = Math.Min (Viewport.Y, viewPortRelativeRegion.Y);
  65. int w = Math.Max (Viewport.Width, viewPortRelativeRegion.Width);
  66. int h = Math.Max (Viewport.Height, viewPortRelativeRegion.Height);
  67. NeedsDrawRect = new (x, y, w, h);
  68. }
  69. // Do not set on Margin - it will be drawn in a separate pass.
  70. if (Border is { } && Border.Thickness != Thickness.Empty)
  71. {
  72. Border?.SetNeedsDraw ();
  73. }
  74. if (Padding is { } && Padding.Thickness != Thickness.Empty)
  75. {
  76. Padding?.SetNeedsDraw ();
  77. }
  78. SuperView?.SetSubViewNeedsDrawDownHierarchy ();
  79. if (this is Adornment adornment)
  80. {
  81. adornment.Parent?.SetSubViewNeedsDrawDownHierarchy ();
  82. }
  83. foreach (View subview in InternalSubViews.Snapshot ())
  84. {
  85. if (subview.Frame.IntersectsWith (viewPortRelativeRegion))
  86. {
  87. Rectangle subviewRegion = Rectangle.Intersect (subview.Frame, viewPortRelativeRegion);
  88. subviewRegion.X -= subview.Frame.X;
  89. subviewRegion.Y -= subview.Frame.Y;
  90. subview.SetNeedsDraw (subviewRegion);
  91. }
  92. }
  93. }
  94. /// <summary>INTERNAL: Clears <see cref="NeedsDraw"/> and <see cref="SubViewNeedsDraw"/> for this view and all SubViews.</summary>
  95. /// <remarks>
  96. /// See <see cref="SubViewNeedsDraw"/> is a cached value that is set when any subview or adornment requests a redraw.
  97. /// It may not always be in sync with the actual state of the subviews.
  98. /// </remarks>
  99. internal void ClearNeedsDraw ()
  100. {
  101. NeedsDrawRect = Rectangle.Empty;
  102. Margin?.ClearNeedsDraw ();
  103. Border?.ClearNeedsDraw ();
  104. Padding?.ClearNeedsDraw ();
  105. foreach (View subview in InternalSubViews.Snapshot ())
  106. {
  107. subview.ClearNeedsDraw ();
  108. }
  109. SubViewNeedsDraw = false;
  110. // This ensures LineCanvas' get redrawn
  111. if (!SuperViewRendersLineCanvas)
  112. {
  113. LineCanvas.Clear ();
  114. }
  115. }
  116. // NOTE: SubViewNeedsDraw is decoupled from the actual state of the subviews (and adornments).
  117. // It is a performance optimization to avoid having to traverse all subviews and adornments to check if any need redraw.
  118. // As a result the code is fragile and can get out of sync; care must be taken to ensure it is set and cleared correctly.
  119. /// <summary>
  120. /// INTERNAL: Gets whether any SubViews need to be redrawn.
  121. /// </summary>
  122. /// <remarks>
  123. /// See <see cref="SubViewNeedsDraw"/> is a cached value that is set when any subview or adornment requests a redraw.
  124. /// It may not always be in sync with the actual state of the subviews.
  125. /// </remarks>
  126. internal bool SubViewNeedsDraw { get; private set; }
  127. /// <summary>INTERNAL: Sets <see cref="SubViewNeedsDraw"/> to <see langword="true"/> for this View and all Superviews.</summary>
  128. /// <remarks>
  129. /// See <see cref="SubViewNeedsDraw"/> is a cached value that is set when any subview or adornment requests a redraw.
  130. /// It may not always be in sync with the actual state of the subviews.
  131. /// </remarks>
  132. internal void SetSubViewNeedsDrawDownHierarchy ()
  133. {
  134. if (!Visible)
  135. {
  136. return;
  137. }
  138. SubViewNeedsDraw = true;
  139. if (this is Adornment adornment)
  140. {
  141. adornment.Parent?.SetSubViewNeedsDrawDownHierarchy ();
  142. }
  143. if (SuperView is { SubViewNeedsDraw: false })
  144. {
  145. SuperView.SetSubViewNeedsDrawDownHierarchy ();
  146. }
  147. }
  148. }