diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index 30c1384a3..7a359eb61 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -535,7 +535,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) return firstIteration; } - LayoutAndDraw (); + LayoutAndDraw (TopLevels.Any (v => v.NeedsLayout || v.NeedsDraw)); if (PositionCursor ()) { diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index a8d2d4bf8..84f40c337 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -1,5 +1,6 @@ #nullable enable using System.ComponentModel; +using System.Diagnostics; namespace Terminal.Gui.ViewBase; @@ -111,6 +112,26 @@ public partial class View // Drawing APIs Border?.AdvanceDrawIndicator (); ClearNeedsDraw (); + + if (this is not Adornment && SuperView is not Adornment) + { + // Parent + Debug.Assert (Margin!.Parent == this); + Debug.Assert (Border!.Parent == this); + Debug.Assert (Padding!.Parent == this); + + // SubViewNeedsDraw is set to false by ClearNeedsDraw. + Debug.Assert (SubViewNeedsDraw == false); + Debug.Assert (Margin!.SubViewNeedsDraw == false); + Debug.Assert (Border!.SubViewNeedsDraw == false); + Debug.Assert (Padding!.SubViewNeedsDraw == false); + + // NeedsDraw is set to false by ClearNeedsDraw. + Debug.Assert (NeedsDraw == false); + Debug.Assert (Margin!.NeedsDraw == false); + Debug.Assert (Border!.NeedsDraw == false); + Debug.Assert (Padding!.NeedsDraw == false); + } } // ------------------------------------ @@ -131,6 +152,11 @@ public partial class View // Drawing APIs private void DoDrawAdornmentsSubViews () { + if (Border?.NeedsLayout == true) + { + Border.Layout (); + } + // NOTE: We do not support subviews of Margin? if (Border?.SubViews is { } && Border.Thickness != Thickness.Empty) @@ -151,6 +177,11 @@ public partial class View // Drawing APIs SetClip (saved); } + if (Padding?.NeedsLayout == true) + { + Padding.Layout (); + } + if (Padding?.SubViews is { } && Padding.Thickness != Thickness.Empty) { foreach (View subview in Padding.SubViews) @@ -720,8 +751,7 @@ public partial class View // Drawing APIs /// public bool NeedsDraw { - // TODO: Figure out if we can decouple NeedsDraw from NeedsLayout. - get => Visible && (NeedsDrawRect != Rectangle.Empty || NeedsLayout); + get => Visible && (NeedsDrawRect != Rectangle.Empty || Margin?.NeedsDraw == true || Border?.NeedsDraw == true || Padding?.NeedsDraw == true); set { if (value) @@ -807,7 +837,7 @@ public partial class View // Drawing APIs } // There was multiple enumeration error here, so calling ToArray - probably a stop gap - foreach (View subview in InternalSubViews) + foreach (View subview in InternalSubViews.ToArray ()) { if (subview.Frame.IntersectsWith (viewPortRelativeRegion)) { @@ -846,17 +876,17 @@ public partial class View // Drawing APIs NeedsDrawRect = Rectangle.Empty; SubViewNeedsDraw = false; - if (Margin is { } && Margin.Thickness != Thickness.Empty) + if (Margin is { } && (Margin.Thickness != Thickness.Empty || Margin.SubViewNeedsDraw || Margin.NeedsDraw)) { Margin?.ClearNeedsDraw (); } - if (Border is { } && Border.Thickness != Thickness.Empty) + if (Border is { } && (Border.Thickness != Thickness.Empty || Border.SubViewNeedsDraw || Border.NeedsDraw)) { Border?.ClearNeedsDraw (); } - if (Padding is { } && Padding.Thickness != Thickness.Empty) + if (Padding is { } && (Padding.Thickness != Thickness.Empty || Padding.SubViewNeedsDraw || Padding.NeedsDraw)) { Padding?.ClearNeedsDraw (); } @@ -876,7 +906,6 @@ public partial class View // Drawing APIs { LineCanvas.Clear (); } - } #endregion NeedsDraw diff --git a/Terminal.Gui/ViewBase/View.Layout.cs b/Terminal.Gui/ViewBase/View.Layout.cs index 41339fdf8..a551ee2ec 100644 --- a/Terminal.Gui/ViewBase/View.Layout.cs +++ b/Terminal.Gui/ViewBase/View.Layout.cs @@ -419,7 +419,10 @@ public partial class View // Layout APIs { LayoutSubViews (); - // Debug.Assert(!NeedsLayout); + // A layout was performed so a draw is needed + // NeedsLayout may still be true if a dependent View still needs layout after SubViewsLaidOut event + SetNeedsDraw (); + return true; } diff --git a/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs b/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs index 1a0839eb3..2f425ccd4 100644 --- a/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs +++ b/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs @@ -10,7 +10,7 @@ public class NeedsDrawTests View view = new () { Width = 0, Height = 0 }; view.BeginInit (); view.EndInit (); - Assert.True (view.NeedsDraw); + Assert.False (view.NeedsDraw); //Assert.False (view.SubViewNeedsDraw); } @@ -70,14 +70,16 @@ public class NeedsDrawTests view.NeedsDraw = false; view.BeginInit (); - Assert.True (view.NeedsDraw); // Because layout is still needed + Assert.False (view.NeedsDraw); // Because layout is still needed view.Layout (); - Assert.False (view.NeedsDraw); + // NeedsDraw is true after layout and NeedsLayout is false if SubViewsLaidOut doesn't call SetNeedsLayout + Assert.True (view.NeedsDraw); + Assert.False (view.NeedsLayout); } [Fact] - public void NeedsDraw_False_After_EndInit () + public void NeedsDraw_True_After_EndInit_Where_Call_Layout () { var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; Assert.True (view.NeedsDraw); @@ -96,7 +98,7 @@ public class NeedsDrawTests } [Fact] - public void NeedsDraw_After_SetLayoutNeeded () + public void NeedsDraw_After_SetLayoutNeeded_And_Layout () { var view = new View { Width = 2, Height = 2 }; Assert.True (view.NeedsDraw); @@ -107,8 +109,12 @@ public class NeedsDrawTests Assert.False (view.NeedsLayout); view.SetNeedsLayout (); - Assert.True (view.NeedsDraw); + Assert.False (view.NeedsDraw); Assert.True (view.NeedsLayout); + + view.Layout (); + Assert.True (view.NeedsDraw); + Assert.False (view.NeedsLayout); } [Fact] @@ -121,21 +127,27 @@ public class NeedsDrawTests Assert.False (view.NeedsDraw); Assert.False (view.NeedsLayout); - // SRL won't change anything since the view is Absolute + // SRL won't change anything since the view frame wasn't changed view.SetRelativeLayout (Application.Screen.Size); Assert.False (view.NeedsDraw); view.SetNeedsLayout (); - // SRL won't change anything since the view is Absolute + // SRL won't change anything since the view frame wasn't changed + // SRL doesn't depend on NeedsLayout, but LayoutSubViews does view.SetRelativeLayout (Application.Screen.Size); + Assert.False (view.NeedsDraw); + Assert.True (view.NeedsLayout); + + view.Layout (); Assert.True (view.NeedsDraw); + Assert.False (view.NeedsLayout); view.NeedsDraw = false; - // SRL won't change anything since the view is Absolute. However, Layout has not been called + // SRL won't change anything since the view frame wasn't changed. However, Layout has not been called view.SetRelativeLayout (new (10, 10)); - Assert.True (view.NeedsDraw); + Assert.False (view.NeedsDraw); } [Fact] @@ -149,17 +161,20 @@ public class NeedsDrawTests Width = Dim.Fill (), Height = Dim.Fill () }; - Assert.True (superView.NeedsDraw); + + // A layout wasn't called yet, so NeedsDraw is still empty + Assert.False (superView.NeedsDraw); superView.Add (view); - Assert.True (view.NeedsDraw); - Assert.True (superView.NeedsDraw); + // A layout wasn't called yet, so NeedsDraw is still empty + Assert.False (view.NeedsDraw); + Assert.False (superView.NeedsDraw); superView.BeginInit (); - Assert.True (view.NeedsDraw); - Assert.True (superView.NeedsDraw); + Assert.False (view.NeedsDraw); + Assert.False (superView.NeedsDraw); - superView.EndInit (); + superView.EndInit (); // Call Layout Assert.True (view.NeedsDraw); Assert.True (superView.NeedsDraw); @@ -177,9 +192,10 @@ public class NeedsDrawTests Width = Dim.Fill (), Height = Dim.Fill () }; - Assert.True (superView.NeedsDraw); + Assert.False (superView.NeedsDraw); superView.Layout (); + Assert.True (superView.NeedsDraw); superView.NeedsDraw = false; superView.SetRelativeLayout (new (10, 10));