View.NeedsDraw.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. namespace Terminal.Gui.ViewBase;
  2. public partial class View
  3. {
  4. // TODO: Change NeedsDraw to use a Region instead of Rectangle
  5. // TODO: Make _needsDrawRect nullable instead of relying on Empty
  6. // TODO: If null, it means ?
  7. // TODO: If Empty, it means no need to redraw
  8. // TODO: If not Empty, it means the region that needs to be redrawn
  9. /// <summary>
  10. /// The viewport-relative region that needs to be redrawn. Marked internal for unit tests.
  11. /// </summary>
  12. internal Rectangle NeedsDrawRect { get; set; } = Rectangle.Empty;
  13. /// <summary>Gets or sets whether the view needs to be redrawn.</summary>
  14. /// <remarks>
  15. /// <para>
  16. /// Will be <see langword="true"/> if the <see cref="NeedsLayout"/> property is <see langword="true"/> or if
  17. /// any part of the view's <see cref="Viewport"/> needs to be redrawn.
  18. /// </para>
  19. /// <para>
  20. /// Setting has no effect on <see cref="NeedsLayout"/>.
  21. /// </para>
  22. /// </remarks>
  23. public bool NeedsDraw
  24. {
  25. get => Visible && (NeedsDrawRect != Rectangle.Empty || Margin?.NeedsDraw == true || Border?.NeedsDraw == true || Padding?.NeedsDraw == true);
  26. set
  27. {
  28. if (value)
  29. {
  30. SetNeedsDraw ();
  31. }
  32. else
  33. {
  34. ClearNeedsDraw ();
  35. }
  36. }
  37. }
  38. // TODO: This property is decoupled from the actual state of the subviews (and adornments)
  39. // TODO: It is a 'cache' that is set when any subview or adornment requests a redraw
  40. // TODO: As a result the code is fragile and can get out of sync.
  41. // TODO: Consider making this a computed property that checks all subviews and adornments for their NeedsDraw state
  42. // TODO: But that may have performance implications.
  43. /// <summary>Gets whether any SubViews need to be redrawn.</summary>
  44. public bool SubViewNeedsDraw { get; private set; }
  45. /// <summary>Sets that the <see cref="Viewport"/> of this View needs to be redrawn.</summary>
  46. /// <remarks>
  47. /// If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), this method
  48. /// does nothing.
  49. /// </remarks>
  50. public void SetNeedsDraw ()
  51. {
  52. Rectangle viewport = Viewport;
  53. if (!Visible || (NeedsDrawRect != Rectangle.Empty && viewport.IsEmpty))
  54. {
  55. // This handles the case where the view has not been initialized yet
  56. return;
  57. }
  58. SetNeedsDraw (viewport);
  59. }
  60. /// <summary>Expands the area of this view needing to be redrawn to include <paramref name="viewPortRelativeRegion"/>.</summary>
  61. /// <remarks>
  62. /// <para>
  63. /// The location of <paramref name="viewPortRelativeRegion"/> is relative to the View's <see cref="Viewport"/>.
  64. /// </para>
  65. /// <para>
  66. /// If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), the area to be
  67. /// redrawn will be the <paramref name="viewPortRelativeRegion"/>.
  68. /// </para>
  69. /// </remarks>
  70. /// <param name="viewPortRelativeRegion">The <see cref="Viewport"/>relative region that needs to be redrawn.</param>
  71. public void SetNeedsDraw (Rectangle viewPortRelativeRegion)
  72. {
  73. if (!Visible)
  74. {
  75. return;
  76. }
  77. if (NeedsDrawRect.IsEmpty)
  78. {
  79. NeedsDrawRect = viewPortRelativeRegion;
  80. }
  81. else
  82. {
  83. int x = Math.Min (Viewport.X, viewPortRelativeRegion.X);
  84. int y = Math.Min (Viewport.Y, viewPortRelativeRegion.Y);
  85. int w = Math.Max (Viewport.Width, viewPortRelativeRegion.Width);
  86. int h = Math.Max (Viewport.Height, viewPortRelativeRegion.Height);
  87. NeedsDrawRect = new (x, y, w, h);
  88. }
  89. // Do not set on Margin - it will be drawn in a separate pass.
  90. if (Border is { } && Border.Thickness != Thickness.Empty)
  91. {
  92. Border?.SetNeedsDraw ();
  93. }
  94. if (Padding is { } && Padding.Thickness != Thickness.Empty)
  95. {
  96. Padding?.SetNeedsDraw ();
  97. }
  98. SuperView?.SetSubViewNeedsDraw ();
  99. if (this is Adornment adornment)
  100. {
  101. adornment.Parent?.SetSubViewNeedsDraw ();
  102. }
  103. // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap
  104. foreach (View subview in InternalSubViews.Snapshot ())
  105. {
  106. if (subview.Frame.IntersectsWith (viewPortRelativeRegion))
  107. {
  108. Rectangle subviewRegion = Rectangle.Intersect (subview.Frame, viewPortRelativeRegion);
  109. subviewRegion.X -= subview.Frame.X;
  110. subviewRegion.Y -= subview.Frame.Y;
  111. subview.SetNeedsDraw (subviewRegion);
  112. }
  113. }
  114. }
  115. /// <summary>Sets <see cref="SubViewNeedsDraw"/> to <see langword="true"/> for this View and all Superviews.</summary>
  116. public void SetSubViewNeedsDraw ()
  117. {
  118. if (!Visible)
  119. {
  120. return;
  121. }
  122. SubViewNeedsDraw = true;
  123. if (this is Adornment adornment)
  124. {
  125. adornment.Parent?.SetSubViewNeedsDraw ();
  126. }
  127. if (SuperView is { SubViewNeedsDraw: false })
  128. {
  129. SuperView.SetSubViewNeedsDraw ();
  130. }
  131. }
  132. /// <summary>Clears <see cref="NeedsDraw"/> and <see cref="SubViewNeedsDraw"/>.</summary>
  133. protected void ClearNeedsDraw ()
  134. {
  135. NeedsDrawRect = Rectangle.Empty;
  136. Margin?.ClearNeedsDraw ();
  137. Border?.ClearNeedsDraw ();
  138. Padding?.ClearNeedsDraw ();
  139. // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap
  140. foreach (View subview in InternalSubViews.Snapshot ())
  141. {
  142. subview.ClearNeedsDraw ();
  143. }
  144. SubViewNeedsDraw = false;
  145. // DO NOT clear SuperView.SubViewNeedsDraw here!
  146. // The SuperView is responsible for clearing its own SubViewNeedsDraw flag.
  147. // Previously this code cleared it:
  148. //if (SuperView is { })
  149. //{
  150. // SuperView.SubViewNeedsDraw = false;
  151. //}
  152. // This caused a bug where drawing one subview would incorrectly clear the SuperView's
  153. // SubViewNeedsDraw flag even when sibling subviews still needed drawing.
  154. //
  155. // The SuperView will clear its own SubViewNeedsDraw after all its subviews are drawn,
  156. // either via:
  157. // 1. The superview's own Draw() method calling ClearNeedsDraw()
  158. // 2. The static View.Draw(peers) method calling ClearNeedsDraw() on all peers
  159. // This ensures LineCanvas' get redrawn
  160. if (!SuperViewRendersLineCanvas)
  161. {
  162. LineCanvas.Clear ();
  163. }
  164. }
  165. }