diff --git a/Terminal.Gui/View/Adornment/Adornment.cs b/Terminal.Gui/View/Adornment/Adornment.cs index 08ff83691..0f30216f9 100644 --- a/Terminal.Gui/View/Adornment/Adornment.cs +++ b/Terminal.Gui/View/Adornment/Adornment.cs @@ -1,6 +1,7 @@ #nullable enable using System.ComponentModel; using Terminal.Gui; +using static Terminal.Gui.SpinnerStyle; using Attribute = Terminal.Gui.Attribute; /// @@ -15,7 +16,7 @@ using Attribute = Terminal.Gui.Attribute; /// mouse input. Each can be customized by manipulating their Subviews. /// /// -public class Adornment : View +public class Adornment : View, IDesignable { /// public Adornment () @@ -63,6 +64,7 @@ public class Adornment : View { Parent?.SetAdornmentFrames (); SetLayoutNeeded (); + SetNeedsDisplay (); //Parent?.SetLayoutNeeded (); OnThicknessChanged (); @@ -88,9 +90,12 @@ public class Adornment : View /// Adornments cannot be used as sub-views (see ); setting this property will throw /// . /// + /// + /// While there are no real use cases for an Adornment being a subview, it is not explicitly dis-allowed to support testing. E.g. in AllViewsTester. + /// public override View? SuperView { - get => null!; + get => base.SuperView!; set => throw new InvalidOperationException (@"Adornments can not be Subviews or have SuperViews. Use Parent instead."); } @@ -115,7 +120,7 @@ public class Adornment : View /// public override Rectangle Viewport { - get => Frame with { Location = Point.Empty }; + get => base.Viewport; set => throw new InvalidOperationException (@"The Viewport of an Adornment cannot be modified."); } @@ -124,6 +129,14 @@ public class Adornment : View { if (Parent is null) { + // While there are no real use cases for an Adornment being a subview, we support it for + // testing. E.g. in AllViewsTester. + if (SuperView is { }) + { + Point super = SuperView.ViewportToScreen (Frame.Location); + + return new (super, Frame.Size); + } return Frame; } @@ -254,4 +267,15 @@ public class Adornment : View // } //} #endregion Mouse Support + + + /// + bool IDesignable.EnableForDesign () + { + Thickness = new (3); + Frame = new (0, 0, 10, 10); + Diagnostics = ViewDiagnosticFlags.Padding; + + return true; + } } diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index 9a36b583e..e37bbccce 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -191,7 +191,7 @@ public class Border : Adornment // TODO: Make Border.LineStyle inherit from the SuperView hierarchy // TODO: Right now, Window and FrameView use CM to set BorderStyle, which negates // TODO: all this. - return Parent!.SuperView?.BorderStyle ?? LineStyle.None; + return Parent?.SuperView?.BorderStyle ?? LineStyle.None; } set => _lineStyle = value; } @@ -634,12 +634,15 @@ public class Border : Adornment int maxTitleWidth = Math.Max ( 0, Math.Min ( - Parent!.TitleTextFormatter.FormatAndGetSize ().Width, + Parent?.TitleTextFormatter.FormatAndGetSize ().Width ?? 0, Math.Min (screenBounds.Width - 4, borderBounds.Width - 4) ) ); - Parent.TitleTextFormatter.ConstrainToSize = new (maxTitleWidth, 1); + if (Parent is { }) + { + Parent.TitleTextFormatter.ConstrainToSize = new (maxTitleWidth, 1); + } int sideLineLength = borderBounds.Height; bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 }; @@ -678,7 +681,7 @@ public class Border : Adornment } } - if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title) && !string.IsNullOrEmpty (Parent?.Title)) + if (Parent is {} && canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title) && !string.IsNullOrEmpty (Parent?.Title)) { Attribute focus = Parent.GetNormalColor (); diff --git a/Terminal.Gui/Views/ColorPicker.16.cs b/Terminal.Gui/Views/ColorPicker.16.cs index 5504e623e..2bb0492d3 100644 --- a/Terminal.Gui/Views/ColorPicker.16.cs +++ b/Terminal.Gui/Views/ColorPicker.16.cs @@ -135,7 +135,7 @@ public class ColorPicker16 : View { base.OnDrawContent (viewport); - Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ()); + Driver?.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ()); var colorIndex = 0; for (var y = 0; y < Math.Max (2, viewport.Height / BoxHeight); y++) @@ -149,7 +149,7 @@ public class ColorPicker16 : View continue; } - Driver.SetAttribute (new ((ColorName16)foregroundColorIndex, (ColorName16)colorIndex)); + Driver?.SetAttribute (new ((ColorName16)foregroundColorIndex, (ColorName16)colorIndex)); bool selected = x == Cursor.X && y == Cursor.Y; DrawColorBox (x, y, selected); colorIndex++; @@ -223,7 +223,7 @@ public class ColorPicker16 : View for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++) { Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY); - Driver.AddRune ((Rune)' '); + Driver?.AddRune ((Rune)' '); index++; } } diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index 5acb61f10..51eb4587e 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -103,7 +103,7 @@ public partial class ColorPicker : View { base.OnDrawContent (viewport); Attribute normal = GetNormalColor (); - Driver.SetAttribute (new (SelectedColor, normal.Background)); + Driver?.SetAttribute (new (SelectedColor, normal.Background)); int y = _bars.Count + (Style.ShowColorName ? 1 : 0); AddRune (13, y, (Rune)'■'); } diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 0b08e1f04..c69f482d6 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -997,11 +997,11 @@ public class TextField : View } } - Driver.SetAttribute (GetFocusColor ()); + Driver?.SetAttribute (GetFocusColor ()); for (int i = col; i < width; i++) { - Driver.AddRune ((Rune)' '); + Driver?.AddRune ((Rune)' '); } PositionCursor (); diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index 804ab8c3c..d0b286d33 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -360,7 +360,6 @@ public class AllViewsTester : Scenario // Instantiate view var view = (View)Activator.CreateInstance (type); - if (view is IDesignable designable) { designable.EnableForDesign (ref _demoText); @@ -384,6 +383,7 @@ public class AllViewsTester : Scenario view.Initialized += CurrentView_Initialized; view.LayoutComplete += CurrentView_LayoutComplete; + view.Id = "_curView"; _curView = view; _hostPane.Add (_curView); } diff --git a/UnitTests/View/Draw/AllViewsDrawTests.cs b/UnitTests/View/Draw/AllViewsDrawTests.cs new file mode 100644 index 000000000..183499777 --- /dev/null +++ b/UnitTests/View/Draw/AllViewsDrawTests.cs @@ -0,0 +1,47 @@ +using Xunit.Abstractions; + +namespace Terminal.Gui.LayoutTests; + +public class AllViewsDrawTests (ITestOutputHelper _output) : TestsAllViews +{ + [Theory] + [MemberData (nameof (AllViewTypes))] + public void AllViews_Does_Not_Layout (Type viewType) + { + var view = (View)CreateInstanceIfNotGeneric (viewType); + + if (view == null) + { + _output.WriteLine ($"Ignoring {viewType} - It's a Generic"); + + return; + } + + if (view is IDesignable designable) + { + designable.EnableForDesign (); + } + + var drawContentCount = 0; + view.DrawContent += (s, e) => drawContentCount++; + + var layoutStartedCount = 0; + view.LayoutStarted += (s, e) => layoutStartedCount++; + + var layoutCompleteCount = 0; + view.LayoutComplete += (s, e) => layoutCompleteCount++; + + view.SetLayoutNeeded (); + view.Layout (); + + Assert.Equal (0, drawContentCount); + Assert.Equal (1, layoutStartedCount); + Assert.Equal (1, layoutCompleteCount); + + view.Draw (); + + Assert.Equal (1, drawContentCount); + Assert.Equal (1, layoutStartedCount); + Assert.Equal (1, layoutCompleteCount); + } +} diff --git a/UnitTests/View/Layout/LayoutTests.cs b/UnitTests/View/Layout/LayoutTests.cs new file mode 100644 index 000000000..91f899241 --- /dev/null +++ b/UnitTests/View/Layout/LayoutTests.cs @@ -0,0 +1,41 @@ +using Xunit.Abstractions; + +namespace Terminal.Gui.LayoutTests; + +public class LayoutTests (ITestOutputHelper _output) : TestsAllViews +{ + [Theory] + [MemberData (nameof (AllViewTypes))] + public void AllViews_Layout_Does_Not_Draw (Type viewType) + { + var view = (View)CreateInstanceIfNotGeneric (viewType); + + if (view == null) + { + _output.WriteLine ($"Ignoring {viewType} - It's a Generic"); + + return; + } + + if (view is IDesignable designable) + { + designable.EnableForDesign (); + } + + var drawContentCount = 0; + view.DrawContent += (s, e) => drawContentCount++; + + var layoutStartedCount = 0; + view.LayoutStarted += (s, e) => layoutStartedCount++; + + var layoutCompleteCount = 0; + view.LayoutComplete += (s, e) => layoutCompleteCount++; + + view.SetLayoutNeeded (); + view.Layout (); + + Assert.Equal (0, drawContentCount); + Assert.Equal (1, layoutStartedCount); + Assert.Equal (1, layoutCompleteCount); + } +} diff --git a/UnitTests/View/Layout/SetLayoutTests.cs b/UnitTests/View/Layout/SetLayoutTests.cs index bef53d71d..856f67c48 100644 --- a/UnitTests/View/Layout/SetLayoutTests.cs +++ b/UnitTests/View/Layout/SetLayoutTests.cs @@ -807,4 +807,6 @@ public class SetLayoutTests (ITestOutputHelper output) Assert.Equal (19, v2.Frame.Height); t.Dispose (); } + + }