namespace Terminal.Gui.ViewBase;
public partial class View
{
// NOTE: NeedsDrawRect is not currently used to clip drawing to only the invalidated region.
// It is only used within SetNeedsDraw to propagate redraw requests to subviews.
// NOTE: Consider changing NeedsDrawRect from Rectangle to Region for more precise invalidation
// NeedsDraw is already efficiently cached via NeedsDrawRect. It checks:
// 1. NeedsDrawRect (cached by SetNeedsDraw/ClearNeedsDraw)
// 2. Adornment NeedsDraw flags (each cached separately)
///
/// INTERNAL: Gets the viewport-relative region that needs to be redrawn.
///
internal Rectangle NeedsDrawRect { get; private set; } = Rectangle.Empty;
/// Gets whether the view needs to be redrawn.
///
///
/// Will be if the property is or if
/// any part of the view's needs to be redrawn.
///
///
/// Setting has no effect on .
///
///
public bool NeedsDraw => Visible && (NeedsDrawRect != Rectangle.Empty || Margin?.NeedsDraw == true || Border?.NeedsDraw == true || Padding?.NeedsDraw == true);
/// Sets to indicating the of this View needs to be redrawn.
///
/// If the view is not visible ( is ), this method
/// does nothing.
///
public void SetNeedsDraw ()
{
Rectangle viewport = Viewport;
if (!Visible || (NeedsDrawRect != Rectangle.Empty && viewport.IsEmpty))
{
// This handles the case where the view has not been initialized yet
return;
}
SetNeedsDraw (viewport);
}
/// Expands the area of this view needing to be redrawn to include .
///
///
/// The location of is relative to the View's .
///
///
/// If the view has not been initialized ( is ), the area to be
/// redrawn will be the .
///
///
/// The relative region that needs to be redrawn.
public void SetNeedsDraw (Rectangle viewPortRelativeRegion)
{
if (!Visible)
{
return;
}
if (NeedsDrawRect.IsEmpty)
{
NeedsDrawRect = viewPortRelativeRegion;
}
else
{
int x = Math.Min (Viewport.X, viewPortRelativeRegion.X);
int y = Math.Min (Viewport.Y, viewPortRelativeRegion.Y);
int w = Math.Max (Viewport.Width, viewPortRelativeRegion.Width);
int h = Math.Max (Viewport.Height, viewPortRelativeRegion.Height);
NeedsDrawRect = new (x, y, w, h);
}
// Do not set on Margin - it will be drawn in a separate pass.
if (Border is { } && Border.Thickness != Thickness.Empty)
{
Border?.SetNeedsDraw ();
}
if (Padding is { } && Padding.Thickness != Thickness.Empty)
{
Padding?.SetNeedsDraw ();
}
SuperView?.SetSubViewNeedsDrawDownHierarchy ();
if (this is Adornment adornment)
{
adornment.Parent?.SetSubViewNeedsDrawDownHierarchy ();
}
foreach (View subview in InternalSubViews.Snapshot ())
{
if (subview.Frame.IntersectsWith (viewPortRelativeRegion))
{
Rectangle subviewRegion = Rectangle.Intersect (subview.Frame, viewPortRelativeRegion);
subviewRegion.X -= subview.Frame.X;
subviewRegion.Y -= subview.Frame.Y;
subview.SetNeedsDraw (subviewRegion);
}
}
}
/// INTERNAL: Clears and for this view and all SubViews.
///
/// See is a cached value that is set when any subview or adornment requests a redraw.
/// It may not always be in sync with the actual state of the subviews.
///
internal void ClearNeedsDraw ()
{
NeedsDrawRect = Rectangle.Empty;
Margin?.ClearNeedsDraw ();
Border?.ClearNeedsDraw ();
Padding?.ClearNeedsDraw ();
foreach (View subview in InternalSubViews.Snapshot ())
{
subview.ClearNeedsDraw ();
}
SubViewNeedsDraw = false;
// This ensures LineCanvas' get redrawn
if (!SuperViewRendersLineCanvas)
{
LineCanvas.Clear ();
}
}
// NOTE: SubViewNeedsDraw is decoupled from the actual state of the subviews (and adornments).
// It is a performance optimization to avoid having to traverse all subviews and adornments to check if any need redraw.
// 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.
///
/// INTERNAL: Gets whether any SubViews need to be redrawn.
///
///
/// See is a cached value that is set when any subview or adornment requests a redraw.
/// It may not always be in sync with the actual state of the subviews.
///
internal bool SubViewNeedsDraw { get; private set; }
/// INTERNAL: Sets to for this View and all Superviews.
///
/// See is a cached value that is set when any subview or adornment requests a redraw.
/// It may not always be in sync with the actual state of the subviews.
///
internal void SetSubViewNeedsDrawDownHierarchy ()
{
if (!Visible)
{
return;
}
SubViewNeedsDraw = true;
if (this is Adornment adornment)
{
adornment.Parent?.SetSubViewNeedsDrawDownHierarchy ();
}
if (SuperView is { SubViewNeedsDraw: false })
{
SuperView.SetSubViewNeedsDrawDownHierarchy ();
}
}
}