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. /// /// 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 (); } } }