From d104a56e42fcc7b1f4f58611cb42e4c2195fe97e Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 15 Oct 2024 17:17:00 -0600 Subject: [PATCH 001/118] WIP - prototyping... --- Terminal.Gui/Application/Application.Run.cs | 20 +-- Terminal.Gui/View/Adornment/Adornment.cs | 26 +-- Terminal.Gui/View/Adornment/Margin.cs | 4 +- Terminal.Gui/View/Layout/Dim.cs | 4 +- Terminal.Gui/View/Layout/Pos.cs | 4 +- Terminal.Gui/View/View.Adornments.cs | 72 ++------ Terminal.Gui/View/View.Content.cs | 7 +- Terminal.Gui/View/View.Drawing.cs | 65 ++++--- Terminal.Gui/View/View.Hierarchy.cs | 6 +- Terminal.Gui/View/View.Layout.cs | 178 ++++++++++++-------- Terminal.Gui/View/View.Text.cs | 6 +- Terminal.Gui/View/View.cs | 4 +- Terminal.Gui/Views/Bar.cs | 4 +- Terminal.Gui/Views/Button.cs | 2 +- Terminal.Gui/Views/CheckBox.cs | 2 +- Terminal.Gui/Views/ColorPicker.16.cs | 4 +- Terminal.Gui/Views/ComboBox.cs | 2 +- Terminal.Gui/Views/ScrollBarView.cs | 2 +- Terminal.Gui/Views/ScrollView.cs | 6 +- Terminal.Gui/Views/Shortcut.cs | 8 +- Terminal.Gui/Views/TileView.cs | 1 + Terminal.Gui/Views/Toplevel.cs | 4 +- Terminal.Gui/Views/Wizard/Wizard.cs | 4 +- UnitTests/Application/ApplicationTests.cs | 4 +- UnitTests/View/Layout/Dim.AutoTests.cs | 1 + UnitTests/View/Layout/Dim.Tests.cs | 14 +- UnitTests/View/Layout/FrameTests.cs | 2 + UnitTests/View/Layout/LayoutTests.cs | 61 +++++++ UnitTests/View/Layout/Pos.CenterTests.cs | 3 +- UnitTests/View/TextTests.cs | 2 +- UnitTests/View/ViewTests.cs | 1 - UnitTests/Views/LabelTests.cs | 4 +- UnitTests/Views/ToplevelTests.cs | 4 +- 33 files changed, 280 insertions(+), 251 deletions(-) diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 966fb04c68..1ac37c79f9 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -80,14 +80,14 @@ public static RunState Begin (Toplevel toplevel) { ArgumentNullException.ThrowIfNull (toplevel); -//#if DEBUG_IDISPOSABLE -// Debug.Assert (!toplevel.WasDisposed); + //#if DEBUG_IDISPOSABLE + // Debug.Assert (!toplevel.WasDisposed); -// if (_cachedRunStateToplevel is { } && _cachedRunStateToplevel != toplevel) -// { -// Debug.Assert (_cachedRunStateToplevel.WasDisposed); -// } -//#endif + // if (_cachedRunStateToplevel is { } && _cachedRunStateToplevel != toplevel) + // { + // Debug.Assert (_cachedRunStateToplevel.WasDisposed); + // } + //#endif // Ensure the mouse is ungrabbed. MouseGrabView = null; @@ -491,11 +491,7 @@ public static void Refresh () { foreach (Toplevel tl in TopLevels.Reverse ()) { - if (tl.LayoutNeeded) - { - tl.LayoutSubviews (); - } - + tl.LayoutSubviews (); tl.Draw (); } diff --git a/Terminal.Gui/View/Adornment/Adornment.cs b/Terminal.Gui/View/Adornment/Adornment.cs index ca90eed765..9d3559f63b 100644 --- a/Terminal.Gui/View/Adornment/Adornment.cs +++ b/Terminal.Gui/View/Adornment/Adornment.cs @@ -55,16 +55,9 @@ public Thickness Thickness if (current != _thickness) { - if (Parent?.IsInitialized == false) - { - // When initialized Parent.LayoutSubViews will cause a LayoutAdornments - Parent?.LayoutAdornments (); - } - else - { - Parent?.SetNeedsLayout (); - Parent?.LayoutSubviews (); - } + Parent?.SetAdornmentFrames (); + Parent?.SetLayoutNeeded (); + //Parent?.LayoutSubviews (); OnThicknessChanged (); } @@ -101,10 +94,10 @@ public override View? SuperView // return null; //} - internal override void LayoutAdornments () - { - /* Do nothing - Adornments do not have Adornments */ - } + //internal override void LayoutAdornments () + //{ + // /* Do nothing - Adornments do not have Adornments */ + //} /// /// Gets the rectangle that describes the area of the Adornment. The Location is always (0,0). @@ -182,10 +175,9 @@ public override void OnDrawContent (Rectangle viewport) if (Driver is { }) { - Driver.Clip = prevClip; + Driver.Clip = prevClip; } - ClearLayoutNeeded (); ClearNeedsDisplay (); } @@ -199,7 +191,7 @@ public override void OnDrawContent (Rectangle viewport) /// public override bool SuperViewRendersLineCanvas { - get => false; + get => false; set => throw new InvalidOperationException (@"Adornment can only render to their Parent or Parent's Superview."); } diff --git a/Terminal.Gui/View/Adornment/Margin.cs b/Terminal.Gui/View/Adornment/Margin.cs index 1f6cc81540..46acbddbb8 100644 --- a/Terminal.Gui/View/Adornment/Margin.cs +++ b/Terminal.Gui/View/Adornment/Margin.cs @@ -117,12 +117,12 @@ public override void OnDrawContent (Rectangle viewport) { IEnumerable subviewsNeedingDraw = Subviews.Where ( view => view.Visible - && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded) + && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.IsLayoutNeeded ()) ); foreach (View view in subviewsNeedingDraw) { - if (view.LayoutNeeded) + if (view.IsLayoutNeeded ()) { view.LayoutSubviews (); } diff --git a/Terminal.Gui/View/Layout/Dim.cs b/Terminal.Gui/View/Layout/Dim.cs index 83d169e2cf..0944fd5bef 100644 --- a/Terminal.Gui/View/Layout/Dim.cs +++ b/Terminal.Gui/View/Layout/Dim.cs @@ -251,7 +251,7 @@ internal virtual int Calculate (int location, int superviewContentSize, View us, } var newDim = new DimCombine (AddOrSubtract.Add, left, right); - (left as DimView)?.Target?.SetNeedsLayout (); + (left as DimView)?.Target?.SetLayoutNeeded (); return newDim; } @@ -276,7 +276,7 @@ internal virtual int Calculate (int location, int superviewContentSize, View us, } var newDim = new DimCombine (AddOrSubtract.Subtract, left, right); - (left as DimView)?.Target?.SetNeedsLayout (); + (left as DimView)?.Target?.SetLayoutNeeded (); return newDim; } diff --git a/Terminal.Gui/View/Layout/Pos.cs b/Terminal.Gui/View/Layout/Pos.cs index b5233a6dcb..b8f47538ea 100644 --- a/Terminal.Gui/View/Layout/Pos.cs +++ b/Terminal.Gui/View/Layout/Pos.cs @@ -366,7 +366,7 @@ public bool Has (out Pos pos) where T : Pos if (left is PosView view) { - view.Target?.SetNeedsLayout (); + view.Target?.SetLayoutNeeded (); } return newPos; @@ -395,7 +395,7 @@ public bool Has (out Pos pos) where T : Pos if (left is PosView view) { - view.Target?.SetNeedsLayout (); + view.Target?.SetLayoutNeeded (); } return newPos; diff --git a/Terminal.Gui/View/View.Adornments.cs b/Terminal.Gui/View/View.Adornments.cs index f3f37d40f8..54f110d2a6 100644 --- a/Terminal.Gui/View/View.Adornments.cs +++ b/Terminal.Gui/View/View.Adornments.cs @@ -163,8 +163,8 @@ protected void OnBorderStyleChanging (CancelEventArgs e) } SetBorderStyle (e.NewValue); - LayoutAdornments (); - SetNeedsLayout (); + SetAdornmentFrames (); + SetLayoutNeeded (); } /// @@ -243,70 +243,22 @@ public Thickness GetAdornmentsThickness () return Margin.Thickness + Border.Thickness + Padding.Thickness; } - /// Lays out the Adornments of the View. - /// - /// Overriden by to do nothing, as does not have adornments. - /// - internal virtual void LayoutAdornments () + /// Sets the Frame's of the Margin, Border, and Padding. + internal void SetAdornmentFrames () { - if (Margin is null) - { - return; // CreateAdornments () has not been called yet - } - - if (Margin.Frame.Size != Frame.Size) - { - Margin.SetFrame (Rectangle.Empty with { Size = Frame.Size }); - Margin.X = 0; - Margin.Y = 0; - Margin.Width = Frame.Size.Width; - Margin.Height = Frame.Size.Height; - } - - Margin.SetNeedsLayout (); - Margin.SetNeedsDisplay (); - - if (IsInitialized) - { - Margin.LayoutSubviews (); - } - - Rectangle border = Margin.Thickness.GetInside (Margin.Frame); - - if (border != Border.Frame) + if (this is Adornment) { - Border.SetFrame (border); - Border.X = border.Location.X; - Border.Y = border.Location.Y; - Border.Width = border.Size.Width; - Border.Height = border.Size.Height; - } - - Border.SetNeedsLayout (); - Border.SetNeedsDisplay (); - - if (IsInitialized) - { - Border.LayoutSubviews (); + // Adornments do not have Adornments + return; } - Rectangle padding = Border.Thickness.GetInside (Border.Frame); - - if (padding != Padding.Frame) + if (Margin is null) { - Padding.SetFrame (padding); - Padding.X = padding.Location.X; - Padding.Y = padding.Location.Y; - Padding.Width = padding.Size.Width; - Padding.Height = padding.Size.Height; + return; // CreateAdornments () has not been called yet } - Padding.SetNeedsLayout (); - Padding.SetNeedsDisplay (); - - if (IsInitialized) - { - Padding.LayoutSubviews (); - } + Margin.SetFrame (Rectangle.Empty with { Size = Frame.Size }); + Border.SetFrame (Margin.Thickness.GetInside (Margin.Frame)); + Padding.SetFrame (Border.Thickness.GetInside (Border.Frame)); } } diff --git a/Terminal.Gui/View/View.Content.cs b/Terminal.Gui/View/View.Content.cs index 95433903ec..ba441f56f8 100644 --- a/Terminal.Gui/View/View.Content.cs +++ b/Terminal.Gui/View/View.Content.cs @@ -151,10 +151,7 @@ public bool ContentSizeTracksViewport if (e.Cancel != true) { - OnResizeNeeded (); - - //SetNeedsLayout (); - //SetNeedsDisplay (); + SetLayoutNeeded (); } return e.Cancel; @@ -311,7 +308,7 @@ private void SetViewport (Rectangle viewport) if (_viewportLocation != viewport.Location) { _viewportLocation = viewport.Location; - SetNeedsLayout (); + SetLayoutNeeded (); } OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport)); diff --git a/Terminal.Gui/View/View.Drawing.cs b/Terminal.Gui/View/View.Drawing.cs index 4401d29580..d5ce99e835 100644 --- a/Terminal.Gui/View/View.Drawing.cs +++ b/Terminal.Gui/View/View.Drawing.cs @@ -1,4 +1,6 @@ #nullable enable +using System.Diagnostics; + namespace Terminal.Gui; public partial class View // Drawing APIs @@ -223,7 +225,7 @@ public void Draw () SetNeedsDisplay (); } - if (!NeedsDisplay && !SubViewNeedsDisplay && !LayoutNeeded) + if (!NeedsDisplay && !SubViewNeedsDisplay && !IsLayoutNeeded ()) { return; } @@ -270,8 +272,6 @@ public void Draw () // Invoke DrawContentCompleteEvent OnDrawContentComplete (Viewport); - // BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details. - ClearLayoutNeeded (); ClearNeedsDisplay (); } @@ -520,39 +520,34 @@ public virtual bool OnDrawAdornments () /// public virtual void OnDrawContent (Rectangle viewport) { - if (NeedsDisplay) + if (!CanBeVisible (this)) { - if (!CanBeVisible (this)) - { - return; - } + return; + } - // BUGBUG: this clears way too frequently. Need to optimize this. - if (SuperView is { } || Arrangement.HasFlag (ViewArrangement.Overlapped)) - { - Clear (); - } + // BUGBUG: this clears way too frequently. Need to optimize this. + if (SuperView is { } || Arrangement.HasFlag (ViewArrangement.Overlapped)) + { + Clear (); + } - if (!string.IsNullOrEmpty (TextFormatter.Text)) - { - if (TextFormatter is { }) - { - TextFormatter.NeedsFormat = true; - } - } + if (!string.IsNullOrEmpty (TextFormatter.Text)) + { + TextFormatter.NeedsFormat = true; + } - // This should NOT clear - // TODO: If the output is not in the Viewport, do nothing - var drawRect = new Rectangle (ContentToScreen (Point.Empty), GetContentSize ()); + // This should NOT clear + // TODO: If the output is not in the Viewport, do nothing + var drawRect = new Rectangle (ContentToScreen (Point.Empty), GetContentSize ()); + + TextFormatter?.Draw ( + drawRect, + HasFocus ? GetFocusColor () : GetNormalColor (), + HasFocus ? GetHotFocusColor () : GetHotNormalColor (), + Rectangle.Empty + ); + SetSubViewNeedsDisplay (); - TextFormatter?.Draw ( - drawRect, - HasFocus ? GetFocusColor () : GetNormalColor (), - HasFocus ? GetHotFocusColor () : GetHotNormalColor (), - Rectangle.Empty - ); - SetSubViewNeedsDisplay (); - } // TODO: Move drawing of subviews to a separate OnDrawSubviews virtual method // Draw subviews @@ -563,14 +558,15 @@ public virtual void OnDrawContent (Rectangle viewport) view => view.Visible && (view.NeedsDisplay || view.SubViewNeedsDisplay - || view.LayoutNeeded + || view.IsLayoutNeeded () || view.Arrangement.HasFlag (ViewArrangement.Overlapped) - )); + )); foreach (View view in subviewsNeedingDraw) { - if (view.LayoutNeeded) + if (view.IsLayoutNeeded ()) { + Debug.WriteLine ("Layout should be de-coupled from drawing"); view.LayoutSubviews (); } @@ -583,6 +579,7 @@ public virtual void OnDrawContent (Rectangle viewport) view.Draw (); } + } } diff --git a/Terminal.Gui/View/View.Hierarchy.cs b/Terminal.Gui/View/View.Hierarchy.cs index 01cf01bebd..0b493d2a1a 100644 --- a/Terminal.Gui/View/View.Hierarchy.cs +++ b/Terminal.Gui/View/View.Hierarchy.cs @@ -86,8 +86,7 @@ public virtual View Add (View view) } CheckDimAuto (); - SetNeedsLayout (); - SetNeedsDisplay (); + view.SetRelativeLayout(GetContentSize()); return view; } @@ -126,7 +125,6 @@ public virtual void OnAdded (SuperViewChangedEventArgs e) { View view = e.SubView; view.IsAdded = true; - view.OnResizeNeeded (); view.Added?.Invoke (this, e); } @@ -178,7 +176,7 @@ public virtual void OnRemoved (SuperViewChangedEventArgs e) } view._superView = null; - SetNeedsLayout (); + SetLayoutNeeded (); SetNeedsDisplay (); foreach (View v in _subviews) diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs index 318b5c993a..5422bdf832 100644 --- a/Terminal.Gui/View/View.Layout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -38,8 +38,8 @@ public partial class View // Layout APIs int targetY, out int nx, out int ny - //, - // out StatusBar? statusBar + //, + // out StatusBar? statusBar ) { int maxDimension; @@ -205,6 +205,7 @@ public Rectangle Frame return; } + // This will set _frame, call SetsNeedsLayout, and raise OnViewportChanged/ViewportChanged SetFrame (value with { Width = Math.Max (value.Width, 0), Height = Math.Max (value.Height, 0) }); // If Frame gets set, set all Pos/Dim to Absolute values. @@ -213,17 +214,21 @@ public Rectangle Frame _width = _frame.Width; _height = _frame.Height; - if (IsInitialized) - { - OnResizeNeeded (); - } - - SetNeedsDisplay (); + SetLayoutNeeded (); } } + /// + /// INTERNAL API - Sets _frame, calls SetsNeedsLayout, and raises OnViewportChanged/ViewportChanged + /// + /// private void SetFrame (in Rectangle frame) { + if (_frame == frame) + { + return; + } + var oldViewport = Rectangle.Empty; if (IsInitialized) @@ -234,6 +239,11 @@ private void SetFrame (in Rectangle frame) // This is the only place where _frame should be set directly. Use Frame = or SetFrame instead. _frame = frame; + SetAdornmentFrames (); + + SetLayoutNeeded (); + + // BUGBUG: When SetFrame is called from Frame_set, this event gets raised BEFORE OnResizeNeeded. Is that OK? OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport)); } @@ -333,7 +343,13 @@ public Pos X _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null"); - OnResizeNeeded (); + if (!IsInitialized) + { + // Supports Unit Tests that don't call Begin/EndInit + SetRelativeLayout (GetBestGuessSuperViewContentSize ()); + } + + SetLayoutNeeded (); } } @@ -375,7 +391,14 @@ public Pos Y } _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null"); - OnResizeNeeded (); + + if (!IsInitialized) + { + // Supports Unit Tests that don't call Begin/EndInit + SetRelativeLayout (GetBestGuessSuperViewContentSize ()); + } + + SetLayoutNeeded (); } } @@ -428,7 +451,13 @@ public Dim? Height // Reset TextFormatter - Will be recalculated in SetTextFormatterSize TextFormatter.ConstrainToHeight = null; - OnResizeNeeded (); + if (!IsInitialized) + { + // Supports Unit Tests that don't call Begin/EndInit + SetRelativeLayout (GetBestGuessSuperViewContentSize ()); + } + + SetLayoutNeeded (); } } @@ -481,7 +510,13 @@ public Dim? Width // Reset TextFormatter - Will be recalculated in SetTextFormatterSize TextFormatter.ConstrainToWidth = null; - OnResizeNeeded (); + if (!IsInitialized) + { + // Supports Unit Tests that don't call Begin/EndInit + SetRelativeLayout (GetBestGuessSuperViewContentSize ()); + } + + SetLayoutNeeded (); } } @@ -520,6 +555,9 @@ public Dim? Width /// other subviews, on /// will be called for that subview. /// + /// + /// Some subviews may have SetRelativeLayout called on them as a side effect, particularly in DimAuto scenarios. + /// /// /// /// The size of the SuperView's content (nominally the same as this.SuperView.GetContentSize ()). @@ -565,6 +603,7 @@ internal void SetRelativeLayout (Size superviewContentSize) if (Frame != newFrame) { // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height + // This will set _frame, call SetsNeedsLayout, and raise OnViewportChanged/ViewportChanged SetFrame (newFrame); if (_x is PosAbsolute) @@ -591,9 +630,6 @@ internal void SetRelativeLayout (Size superviewContentSize) { SetTitleTextFormatterSize (); } - - SetNeedsLayout (); - SetNeedsDisplay (); } if (TextFormatter.ConstrainToWidth is null) @@ -607,6 +643,7 @@ internal void SetRelativeLayout (Size superviewContentSize) } } + // TODO: remove virtual from this /// /// Invoked when the dimensions of the view have changed, for example in response to the container view or terminal /// resizing. @@ -629,7 +666,7 @@ public virtual void LayoutSubviews () Debug.WriteLine ($"WARNING: LayoutSubviews called before view has been initialized. This is likely a bug in {this}"); } - if (!LayoutNeeded) + if (!IsLayoutNeeded ()) { return; } @@ -639,7 +676,9 @@ public virtual void LayoutSubviews () Size contentSize = GetContentSize (); OnLayoutStarted (new (contentSize)); - LayoutAdornments (); + Margin?.LayoutSubviews (); + Border?.LayoutSubviews (); + Padding?.LayoutSubviews (); // Sort out the dependencies of the X, Y, Width, Height properties HashSet nodes = new (); @@ -654,7 +693,7 @@ public virtual void LayoutSubviews () // If the 'to' is rooted to 'from' it's a special-case. // Use LayoutSubview with the Frame of the 'from'. - if (SuperView is { } && GetTopSuperView () is { } && LayoutNeeded && edges.Count > 0) + if (SuperView is { } && GetTopSuperView () is { } && IsLayoutNeeded () && edges.Count > 0) { foreach ((View from, View to) in edges) { @@ -662,7 +701,7 @@ public virtual void LayoutSubviews () } } - LayoutNeeded = false; + _layoutNeeded = false; OnLayoutComplete (new (contentSize)); } @@ -672,12 +711,8 @@ private void LayoutSubview (View v, Size contentSize) // Note, SetRelativeLayout calls SetTextFormatterSize v.SetRelativeLayout (contentSize); v.LayoutSubviews (); - v.LayoutNeeded = false; } - /// Indicates that the view does not need to be laid out. - protected void ClearLayoutNeeded () { LayoutNeeded = false; } - /// /// Raises the event. Called from before all sub-views /// have been laid out. @@ -690,77 +725,76 @@ private void LayoutSubview (View v, Size contentSize) /// internal virtual void OnLayoutStarted (LayoutEventArgs args) { LayoutStarted?.Invoke (this, args); } - /// - /// Called whenever the view needs to be resized. This is called whenever , - /// , , , or changes. - /// - /// - /// - /// Determines the relative bounds of the and its s, and then calls - /// to update the view. - /// - /// - internal void OnResizeNeeded () - { - // TODO: Identify a real-world use-case where this API should be virtual. - // TODO: Until then leave it `internal` and non-virtual - // Determine our container's ContentSize - - // First try SuperView.Viewport, then Application.Top, then Driver.Viewport. - // Finally, if none of those are valid, use 2048 (for Unit tests). - Size superViewContentSize = SuperView is { IsInitialized: true } ? SuperView.GetContentSize () : - Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.GetContentSize () : - Application.Screen.Size; + // We expose no setter for this to ensure that the ONLY place it's changed is in SetNeedsLayout + private bool _layoutNeeded = true; - SetRelativeLayout (superViewContentSize); + /// + /// Indicates the View's Frame or the relative layout of the View's subviews (including Adornments) have + /// changed since the last time the View was laid out. Used to prevent from needlessly computing + /// layout. + /// + /// + internal bool IsLayoutNeeded () => _layoutNeeded; - if (IsInitialized) + /// + /// INTERNAL API - Sets for this View and all of it's subviews and it's SuperView. + /// The main loop will call SetRelativeLayout and LayoutSubviews for any view with set. + /// + internal void SetLayoutNeeded () + { + if (IsLayoutNeeded ()) { - LayoutAdornments (); + // Prevent infinite recursion + return; } - SetNeedsLayout (); + _layoutNeeded = true; + + // Use a stack to avoid recursion + Stack stack = new Stack (Subviews); - // TODO: This ensures overlapped views are drawn correctly. However, this is inefficient. - // TODO: The correct fix is to implement non-rectangular clip regions: https://github.com/gui-cs/Terminal.Gui/issues/3413 - if (Arrangement.HasFlag (ViewArrangement.Overlapped)) + while (stack.Count > 0) { - foreach (Toplevel v in Application.TopLevels) + View current = stack.Pop (); + if (!current.IsLayoutNeeded ()) { - if (v.Visible && v != this) + current._layoutNeeded = true; + foreach (View subview in current.Subviews) { - v.SetNeedsDisplay (); + stack.Push (subview); } } } - } - internal bool LayoutNeeded { get; private set; } = true; + TextFormatter.NeedsFormat = true; - /// - /// Sets for this View and all of it's subviews and it's SuperView. - /// The main loop will call SetRelativeLayout and LayoutSubviews for any view with set. - /// - internal void SetNeedsLayout () - { - if (LayoutNeeded) + // Use a loop to avoid recursion for superview hierarchy + View? superView = SuperView; + while (superView != null && !superView.IsLayoutNeeded ()) { - return; + superView._layoutNeeded = true; + superView = superView.SuperView; } + } - LayoutNeeded = true; - - foreach (View view in Subviews) - { - view.SetNeedsLayout (); - } + /// + /// INTERNAL API FOR UNIT TESTS - Gets the size of the SuperView's content (nominally the same as + /// the SuperView's ). + /// + /// + private Size GetBestGuessSuperViewContentSize () + { + Size superViewContentSize = SuperView?.GetContentSize () ?? + (Application.Top is { } && Application.Top != this && Application.Top.IsInitialized + ? Application.Top.GetContentSize () + : Application.Screen.Size); - TextFormatter.NeedsFormat = true; - SuperView?.SetNeedsLayout (); + return superViewContentSize; } /// - /// Collects all views and their dependencies from a given starting view for layout purposes. Used by + /// INTERNAL API - Collects all views and their dependencies from a given starting view for layout purposes. Used by /// to create an ordered list of views to layout. /// /// The starting view from which to collect dependencies. diff --git a/Terminal.Gui/View/View.Text.cs b/Terminal.Gui/View/View.Text.cs index 508637b567..aaeadafeaf 100644 --- a/Terminal.Gui/View/View.Text.cs +++ b/Terminal.Gui/View/View.Text.cs @@ -62,7 +62,7 @@ public virtual string Text _text = value; UpdateTextFormatterText (); - OnResizeNeeded (); + SetLayoutNeeded (); #if DEBUG if (_text is { } && string.IsNullOrEmpty (Id)) { @@ -92,7 +92,7 @@ public virtual Alignment TextAlignment { TextFormatter.Alignment = value; UpdateTextFormatterText (); - OnResizeNeeded (); + SetLayoutNeeded (); } } @@ -229,7 +229,7 @@ private void UpdateTextDirection (TextDirection newDirection) { TextFormatter.ConstrainToWidth = null; TextFormatter.ConstrainToHeight = null; - OnResizeNeeded (); + SetLayoutNeeded (); } SetNeedsDisplay (); diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 706409c5b0..0df94b488b 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -122,7 +122,7 @@ public partial class View : Responder, ISupportInitializeNotification /// Points to the current driver in use by the view, it is a convenience property for simplifying the development /// of new views. /// - public static ConsoleDriver Driver => Application.Driver!; + public static ConsoleDriver? Driver => Application.Driver; /// Initializes a new instance of . /// @@ -229,7 +229,7 @@ public virtual void EndInit () // These calls were moved from BeginInit as they access Viewport which is indeterminate until EndInit is called. UpdateTextDirection (TextDirection); UpdateTextFormatterText (); - OnResizeNeeded (); + SetLayoutNeeded (); if (_subviews is { }) { diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index 1511cd68af..cf9108c1b5 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -119,7 +119,7 @@ public Orientation Orientation /// public void OnOrientationChanged (Orientation newOrientation) { - SetNeedsLayout (); + SetLayoutNeeded (); } #endregion @@ -135,7 +135,7 @@ public AlignmentModes AlignmentModes set { _alignmentModes = value; - SetNeedsLayout (); + SetLayoutNeeded (); } } diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index f642f23d9c..f4ea747a7e 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -191,7 +191,7 @@ public bool IsDefault _isDefault = value; UpdateTextFormatterText (); - OnResizeNeeded (); + SetLayoutNeeded (); } } diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index af58ecedd9..6cb77483c2 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -153,7 +153,7 @@ public CheckState CheckedState _checkedState = value; UpdateTextFormatterText (); - OnResizeNeeded (); + SetLayoutNeeded (); EventArgs args = new (in _checkedState); OnCheckedStateChanged (args); diff --git a/Terminal.Gui/Views/ColorPicker.16.cs b/Terminal.Gui/Views/ColorPicker.16.cs index c1cb72cc23..6c9ee05e30 100644 --- a/Terminal.Gui/Views/ColorPicker.16.cs +++ b/Terminal.Gui/Views/ColorPicker.16.cs @@ -28,7 +28,7 @@ public int BoxHeight Width = Dim.Auto (minimumContentDim: _boxWidth * _cols); Height = Dim.Auto (minimumContentDim: _boxHeight * _rows); SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows)); - SetNeedsLayout (); + SetLayoutNeeded (); } } } @@ -45,7 +45,7 @@ public int BoxWidth Width = Dim.Auto (minimumContentDim: _boxWidth * _cols); Height = Dim.Auto (minimumContentDim: _boxHeight * _rows); SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows)); - SetNeedsLayout (); + SetLayoutNeeded (); } } } diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 996056068c..ddda743a7b 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -72,7 +72,7 @@ public ComboBox () } } - SetNeedsLayout (); + SetLayoutNeeded (); SetNeedsDisplay (); ShowHideList (Text); }; diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index f6e8da2961..24dc68ef97 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -225,7 +225,7 @@ public bool ShowScrollIndicator if (IsInitialized) { - SetNeedsLayout (); + SetLayoutNeeded (); if (value) { diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 35d4409e5d..99c79d206f 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -287,7 +287,7 @@ public bool ShowHorizontalScrollIndicator if (value != _showHorizontalScrollIndicator) { _showHorizontalScrollIndicator = value; - SetNeedsLayout (); + SetLayoutNeeded (); if (value) { @@ -322,7 +322,7 @@ public bool ShowVerticalScrollIndicator if (value != _showVerticalScrollIndicator) { _showVerticalScrollIndicator = value; - SetNeedsLayout (); + SetLayoutNeeded (); if (value) { @@ -367,7 +367,7 @@ public override View Add (View view) _contentView.Add (view); } - SetNeedsLayout (); + SetLayoutNeeded (); return view; } diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index f23ec23e33..ce1e0e8ac6 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -412,7 +412,7 @@ public Orientation Orientation public void OnOrientationChanged (Orientation newOrientation) { // TODO: Determine what, if anything, is opinionated about the orientation. - SetNeedsLayout (); + SetLayoutNeeded (); } #endregion @@ -679,9 +679,9 @@ public int MinimumKeyTextSize _minimumKeyTextSize = value; SetKeyViewDefaultLayout (); - CommandView.SetNeedsLayout (); - HelpView.SetNeedsLayout (); - KeyView.SetNeedsLayout (); + CommandView.SetLayoutNeeded (); + HelpView.SetLayoutNeeded (); + KeyView.SetLayoutNeeded (); SetSubViewNeedsDisplay (); } } diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index 60370b2cb5..fdf2c29512 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -155,6 +155,7 @@ public Tile InsertTile (int idx) /// public bool IsRootTileView () { return _parentTileView == null; } + // TODO: Use OnLayoutStarted instead /// public override void LayoutSubviews () { diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index ddee6b0412..291fc955e1 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -272,12 +272,12 @@ out int ny // layoutSubviews = true; //} - if (superView.LayoutNeeded || layoutSubviews) + if (superView.IsLayoutNeeded () || layoutSubviews) { superView.LayoutSubviews (); } - if (LayoutNeeded) + if (IsLayoutNeeded ()) { LayoutSubviews (); } diff --git a/Terminal.Gui/Views/Wizard/Wizard.cs b/Terminal.Gui/Views/Wizard/Wizard.cs index b9bc49bd58..bb19030348 100644 --- a/Terminal.Gui/Views/Wizard/Wizard.cs +++ b/Terminal.Gui/Views/Wizard/Wizard.cs @@ -92,7 +92,7 @@ public Wizard () Closing += Wizard_Closing; TitleChanged += Wizard_TitleChanged; - SetNeedsLayout (); + SetLayoutNeeded (); } /// @@ -545,7 +545,7 @@ private void UpdateButtonsAndTitle () SizeStep (CurrentStep); - SetNeedsLayout (); + SetLayoutNeeded (); LayoutSubviews (); //Draw (); diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index 9fe01a8920..d373c331e8 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -859,10 +859,10 @@ public void Run_Toplevel_With_Modal_View_Does_Not_Refresh_If_Not_Dirty () Application.RaiseMouseEvent (new () { Flags = MouseFlags.ReportMousePosition }); Assert.False (top.NeedsDisplay); Assert.False (top.SubViewNeedsDisplay); - Assert.False (top.LayoutNeeded); + Assert.False (top.IsLayoutNeeded ()); Assert.False (d.NeedsDisplay); Assert.False (d.SubViewNeedsDisplay); - Assert.False (d.LayoutNeeded); + Assert.False (d.IsLayoutNeeded ()); } else { diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index 5f6767310e..1f11997b81 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -314,6 +314,7 @@ public void TextFormatter_Settings_Change_View_Size () view.SetRelativeLayout (Application.Screen.Size); lastSize = view.Frame.Size; view.Text = "*ABCD"; + view.SetRelativeLayout (Application.Screen.Size); Assert.NotEqual (lastSize, view.Frame.Size); } diff --git a/UnitTests/View/Layout/Dim.Tests.cs b/UnitTests/View/Layout/Dim.Tests.cs index c698f24942..1adf57aa0f 100644 --- a/UnitTests/View/Layout/Dim.Tests.cs +++ b/UnitTests/View/Layout/Dim.Tests.cs @@ -185,7 +185,7 @@ public void DimHeight_SetsValue () testValview.Dispose (); testVal = new (1, 2, 3, 4); - testValview = new() { Frame = testVal }; + testValview = new () { Frame = testVal }; dim = Height (testValview); Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ()); testValview.Dispose (); @@ -369,7 +369,7 @@ public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assignin Assert.Equal (18, v6.Frame.Height); // 89*20%=18 w.Width = 200; - Assert.True (t.LayoutNeeded); + Assert.True (t.IsLayoutNeeded ()); w.Height = 200; t.LayoutSubviews (); @@ -582,7 +582,7 @@ public void DimWidth_Equals () Assert.NotEqual (dim1, dim2); testRect1 = new (0, 1, 2, 3); - view1 = new() { Frame = testRect1 }; + view1 = new () { Frame = testRect1 }; testRect2 = new (0, 1, 2, 3); dim1 = Width (view1); dim2 = Width (view1); @@ -591,7 +591,7 @@ public void DimWidth_Equals () Assert.Equal (dim1, dim2); testRect1 = new (0, -1, 2, 3); - view1 = new() { Frame = testRect1 }; + view1 = new () { Frame = testRect1 }; testRect2 = new (0, -1, 2, 3); dim1 = Width (view1); dim2 = Width (view1); @@ -600,9 +600,9 @@ public void DimWidth_Equals () Assert.Equal (dim1, dim2); testRect1 = new (0, -1, 2, 3); - view1 = new() { Frame = testRect1 }; + view1 = new () { Frame = testRect1 }; testRect2 = Rectangle.Empty; - view2 = new() { Frame = testRect2 }; + view2 = new () { Frame = testRect2 }; dim1 = Width (view1); dim2 = Width (view2); Assert.NotEqual (dim1, dim2); @@ -632,7 +632,7 @@ public void DimWidth_SetsValue () testValView.Dispose (); testVal = new (1, 2, 3, 4); - testValView = new() { Frame = testVal }; + testValView = new () { Frame = testVal }; dim = Width (testValView); Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ()); testValView.Dispose (); diff --git a/UnitTests/View/Layout/FrameTests.cs b/UnitTests/View/Layout/FrameTests.cs index 326dd3370d..398585e556 100644 --- a/UnitTests/View/Layout/FrameTests.cs +++ b/UnitTests/View/Layout/FrameTests.cs @@ -40,9 +40,11 @@ public void Frame_Set () var newFrame = new Rectangle (1, 2, 30, 40); var v = new View (); + Assert.Equal (Rectangle.Empty, v.Frame); v.Dispose (); v = new View { Frame = frame }; + Assert.Equal (frame, v.Frame); v.Frame = newFrame; Assert.Equal (newFrame, v.Frame); diff --git a/UnitTests/View/Layout/LayoutTests.cs b/UnitTests/View/Layout/LayoutTests.cs index d965e90e99..a22c113d39 100644 --- a/UnitTests/View/Layout/LayoutTests.cs +++ b/UnitTests/View/Layout/LayoutTests.cs @@ -34,6 +34,67 @@ public void LayoutSubviews_No_SuperView () second.Dispose (); } + [Fact] + public void Add_Does_Not_Call_LayoutSubviews () + { + var superView = new View { Id = "superView" }; + var view = new View { Id = "view" }; + bool layoutStartedRaised = false; + bool layoutCompleteRaised = false; + superView.LayoutStarted += (sender, e) => layoutStartedRaised = true; + superView.LayoutComplete += (sender, e) => layoutCompleteRaised = true; + + superView.Add (view); + + Assert.False (layoutStartedRaised); + Assert.False (layoutCompleteRaised); + + superView.Remove(view); + + superView.BeginInit(); + superView.EndInit (); + + superView.Add (view); + + Assert.False (layoutStartedRaised); + Assert.False (layoutCompleteRaised); + + } + + [Fact] + public void BeginEndInit_Do_Not_Call_LayoutSubviews () + { + var superView = new View { Id = "superView" }; + bool layoutStartedRaised = false; + bool layoutCompleteRaised = false; + superView.LayoutStarted += (sender, e) => layoutStartedRaised = true; + superView.LayoutComplete += (sender, e) => layoutCompleteRaised = true; + superView.BeginInit (); + superView.EndInit (); + Assert.False (layoutStartedRaised); + Assert.False (layoutCompleteRaised); + } + + [Fact] + public void LayoutSubViews_Raises_LayoutStarted_LayoutComplete () + { + var superView = new View { Id = "superView" }; + int layoutStartedRaised = 0; + int layoutCompleteRaised = 0; + superView.LayoutStarted += (sender, e) => layoutStartedRaised++; + superView.LayoutComplete += (sender, e) => layoutCompleteRaised++; + + superView.LayoutSubviews (); + Assert.Equal (1, layoutStartedRaised); + Assert.Equal (1, layoutCompleteRaised); + + superView.BeginInit (); + superView.EndInit (); + superView.LayoutSubviews (); + Assert.Equal (2, layoutStartedRaised); + Assert.Equal (2, layoutCompleteRaised); + } + [Fact] public void LayoutSubviews_RootHas_SuperView () { diff --git a/UnitTests/View/Layout/Pos.CenterTests.cs b/UnitTests/View/Layout/Pos.CenterTests.cs index ef7d5cc8b7..ccd6ba5bda 100644 --- a/UnitTests/View/Layout/Pos.CenterTests.cs +++ b/UnitTests/View/Layout/Pos.CenterTests.cs @@ -61,8 +61,7 @@ public void PosCenter_Bigger_Than_SuperView () var superView = new View { Width = 10, Height = 10 }; var view = new View { X = Center (), Y = Center (), Width = 20, Height = 20 }; superView.Add (view); - superView.BeginInit(); - superView.EndInit(); + superView.LayoutSubviews (); Assert.Equal (-5, view.Frame.Left); Assert.Equal (-5, view.Frame.Top); diff --git a/UnitTests/View/TextTests.cs b/UnitTests/View/TextTests.cs index f5db872584..b54f995168 100644 --- a/UnitTests/View/TextTests.cs +++ b/UnitTests/View/TextTests.cs @@ -715,7 +715,7 @@ public void Excess_Text_Is_Erased_When_The_Width_Is_Reduced () Assert.Equal (new (0, 0, 2, 1), lbl.Frame); Assert.Equal (new (0, 0, 3, 1), lbl._needsDisplayRect); Assert.Equal (new (0, 0, 0, 0), lbl.SuperView._needsDisplayRect); - Assert.True (lbl.SuperView.LayoutNeeded); + Assert.True (lbl.SuperView.IsLayoutNeeded ()); Application.Refresh(); Assert.Equal ("12 ", GetContents ()); diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index 1dc072e3a2..a6cd276709 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -1128,7 +1128,6 @@ public override void OnDrawContent (Rectangle viewport) } } - ClearLayoutNeeded (); ClearNeedsDisplay (); } diff --git a/UnitTests/Views/LabelTests.cs b/UnitTests/Views/LabelTests.cs index 80f1e2b7cb..53e34580ae 100644 --- a/UnitTests/Views/LabelTests.cs +++ b/UnitTests/Views/LabelTests.cs @@ -211,11 +211,11 @@ This TextFormatter (tf2) with fill will be cleared on rewritten. ", ); Assert.False (label.NeedsDisplay); - Assert.False (label.LayoutNeeded); + Assert.False (label.IsLayoutNeeded ()); Assert.False (label.SubViewNeedsDisplay); label.Text = "This label is rewritten."; Assert.True (label.NeedsDisplay); - Assert.True (label.LayoutNeeded); + Assert.True (label.IsLayoutNeeded ()); //Assert.False (label.SubViewNeedsDisplay); label.Draw (); diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index cbf11e4b28..6bf764de6a 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -749,7 +749,7 @@ public void Toplevel_Inside_ScrollView_MouseGrabView () Application.RaiseMouseEvent (new () { ScreenPosition = new (9, 9), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); Assert.Equal (win.Border, Application.MouseGrabView); - top.SetNeedsLayout (); + top.SetLayoutNeeded (); top.LayoutSubviews (); Assert.Equal (new (6, 6, 191, 91), win.Frame); Application.Refresh (); @@ -760,7 +760,7 @@ public void Toplevel_Inside_ScrollView_MouseGrabView () ScreenPosition = new (5, 5), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); Assert.Equal (win.Border, Application.MouseGrabView); - top.SetNeedsLayout (); + top.SetLayoutNeeded (); top.LayoutSubviews (); Assert.Equal (new (2, 2, 195, 95), win.Frame); Application.Refresh (); From 70bf627d62f8b9d08a683e170d2730148e8ee2f9 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 16 Oct 2024 15:40:04 -0600 Subject: [PATCH 002/118] More prototyping --- Terminal.Gui/Application/Application.Run.cs | 48 +++-- .../Application/Application.Screen.cs | 6 +- Terminal.Gui/View/Adornment/Border.cs | 3 +- Terminal.Gui/View/Layout/Dim.cs | 4 +- Terminal.Gui/View/Layout/DimAuto.cs | 44 ++++- Terminal.Gui/View/Layout/DimFunc.cs | 2 +- Terminal.Gui/View/Layout/PosView.cs | 28 ++- Terminal.Gui/View/View.Drawing.cs | 31 ++-- Terminal.Gui/View/View.Hierarchy.cs | 11 +- Terminal.Gui/View/View.Layout.cs | 169 ++++++++++++------ Terminal.Gui/View/View.cs | 3 + Terminal.Gui/Views/Bar.cs | 16 +- Terminal.Gui/Views/Menu/Menu.cs | 4 +- Terminal.Gui/Views/Menu/MenuBar.cs | 1 + Terminal.Gui/Views/Shortcut.cs | 77 ++++---- UICatalog/Scenarios/Navigation.cs | 6 +- UICatalog/UICatalog.cs | 55 +++--- 17 files changed, 338 insertions(+), 170 deletions(-) diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 1ac37c79f9..c268047432 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -94,13 +94,6 @@ public static RunState Begin (Toplevel toplevel) var rs = new RunState (toplevel); - // View implements ISupportInitializeNotification which is derived from ISupportInitialize - if (!toplevel.IsInitialized) - { - toplevel.BeginInit (); - toplevel.EndInit (); - } - #if DEBUG_IDISPOSABLE if (Top is { } && toplevel != Top && !TopLevels.Contains (Top)) { @@ -184,8 +177,19 @@ public static RunState Begin (Toplevel toplevel) } } - toplevel.SetRelativeLayout (Driver!.Screen.Size); - toplevel.LayoutSubviews (); + // View implements ISupportInitializeNotification which is derived from ISupportInitialize + if (!toplevel.IsInitialized) + { + toplevel.BeginInit (); + toplevel.EndInit (); + + if (toplevel.SetRelativeLayout (Driver!.Screen.Size)) + { + toplevel.LayoutSubviews (); + } + + // toplevel.SetLayoutNeeded(); + } // Try to set initial focus to any TabStop if (!toplevel.HasFocus) @@ -195,8 +199,6 @@ public static RunState Begin (Toplevel toplevel) toplevel.OnLoaded (); - Refresh (); - if (PositionCursor ()) { Driver.UpdateCursor (); @@ -489,9 +491,31 @@ public static void Invoke (Action action) /// Triggers a refresh of the entire display. public static void Refresh () { + bool clear = false; + foreach (Toplevel tl in TopLevels.Reverse ()) + { + if (tl.IsLayoutNeeded ()) + { + clear = true; + + if (tl.SetRelativeLayout (Screen.Size)) + { + tl.LayoutSubviews (); + } + } + } + + if (clear) + { + Driver!.ClearContents (); + } + foreach (Toplevel tl in TopLevels.Reverse ()) { - tl.LayoutSubviews (); + if (clear) + { + tl.SetNeedsDisplay(); + } tl.Draw (); } diff --git a/Terminal.Gui/Application/Application.Screen.cs b/Terminal.Gui/Application/Application.Screen.cs index 087021d1c5..940b1b22c4 100644 --- a/Terminal.Gui/Application/Application.Screen.cs +++ b/Terminal.Gui/Application/Application.Screen.cs @@ -35,8 +35,10 @@ public static bool OnSizeChanging (SizeChangedEventArgs args) foreach (Toplevel t in TopLevels) { - t.SetRelativeLayout (args.Size.Value); - t.LayoutSubviews (); + if (t.SetRelativeLayout (args.Size.Value)) + { + t.LayoutSubviews (); + } t.OnSizeChanging (new (args.Size)); } diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index add7527e66..29eb50cbdd 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -565,7 +565,8 @@ out int ny break; } - Application.Refresh (); + + //Application.Refresh (); return true; } diff --git a/Terminal.Gui/View/Layout/Dim.cs b/Terminal.Gui/View/Layout/Dim.cs index 0944fd5bef..4d8534c1f3 100644 --- a/Terminal.Gui/View/Layout/Dim.cs +++ b/Terminal.Gui/View/Layout/Dim.cs @@ -182,9 +182,9 @@ public abstract record Dim : IEqualityOperators /// /// A reference to this instance. /// - public bool Has (out Dim dim) where T : Dim + public bool Has (out T dim) where T : Dim { - dim = this; + dim = (this as T)!; return this switch { diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index f159b6476c..d54043d8a1 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -168,6 +168,7 @@ internal override int Calculate (int location, int superviewContentSize, View us { int width = v.Width!.Calculate (0, superviewContentSize, v, dimension); size = v.X.GetAnchor (0) + width; + } else { @@ -414,11 +415,6 @@ internal override int Calculate (int location, int superviewContentSize, View us List dimAutoSubViews; - if (dimension == Dimension.Width && us.GetType ().Name == "Bar" && us.Subviews.Count == 3) - { - - } - if (dimension == Dimension.Width) { dimAutoSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has (out _)).ToList (); @@ -450,6 +446,44 @@ internal override int Calculate (int location, int superviewContentSize, View us } #endregion + + + #region DimFill + //// [ ] DimFill - Dimension is internally calculated + + //List DimFillSubViews; + + //if (dimension == Dimension.Width) + //{ + // DimFillSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has (out _)).ToList (); + //} + //else + //{ + // DimFillSubViews = includedSubviews.Where (v => v.Height is { } && v.Height.Has (out _)).ToList (); + //} + + //for (var i = 0; i < DimFillSubViews.Count; i++) + //{ + // View v = DimFillSubViews [i]; + + // if (dimension == Dimension.Width) + // { + // v.SetRelativeLayout (new (maxCalculatedSize, 0)); + // } + // else + // { + // v.SetRelativeLayout (new (0, maxCalculatedSize)); + // } + + // int maxDimFill = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; + + // if (maxDimFill > maxCalculatedSize) + // { + // maxCalculatedSize = maxDimFill; + // } + //} + + #endregion } } diff --git a/Terminal.Gui/View/Layout/DimFunc.cs b/Terminal.Gui/View/Layout/DimFunc.cs index c51406f40e..fd93c677a8 100644 --- a/Terminal.Gui/View/Layout/DimFunc.cs +++ b/Terminal.Gui/View/Layout/DimFunc.cs @@ -8,7 +8,7 @@ namespace Terminal.Gui; /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// -/// The function that computes the dimension. +/// The function that computes the dimension. If this function throws public record DimFunc (Func Fn) : Dim { /// diff --git a/Terminal.Gui/View/Layout/PosView.cs b/Terminal.Gui/View/Layout/PosView.cs index 92748d88b3..9e6a3d7c65 100644 --- a/Terminal.Gui/View/Layout/PosView.cs +++ b/Terminal.Gui/View/Layout/PosView.cs @@ -1,4 +1,6 @@ #nullable enable +using System.Diagnostics; + namespace Terminal.Gui; /// @@ -10,19 +12,35 @@ namespace Terminal.Gui; /// methods on the class to create objects instead. /// /// -/// The View the position is anchored to. -/// The side of the View the position is anchored to. -public record PosView (View? View, Side Side) : Pos +public record PosView : Pos { + /// + /// Represents a position that is anchored to the side of another view. + /// + /// + /// + /// This is a low-level API that is typically used internally by the layout system. Use the various static + /// methods on the class to create objects instead. + /// + /// + /// The View the position is anchored to. + /// The side of the View the position is anchored to. + public PosView (View view, Side side) + { + ArgumentNullException.ThrowIfNull (view); + Target = view; + Side = side; + } + /// /// Gets the View the position is anchored to. /// - public View? Target { get; } = View; + public View Target { get; } /// /// Gets the side of the View the position is anchored to. /// - public Side Side { get; } = Side; + public Side Side { get; } /// public override string ToString () diff --git a/Terminal.Gui/View/View.Drawing.cs b/Terminal.Gui/View/View.Drawing.cs index d5ce99e835..6635f79c1a 100644 --- a/Terminal.Gui/View/View.Drawing.cs +++ b/Terminal.Gui/View/View.Drawing.cs @@ -218,19 +218,27 @@ public void Draw () return; } - // TODO: This ensures overlapped views are drawn correctly. However, this is inefficient. - // TODO: The correct fix is to implement non-rectangular clip regions: https://github.com/gui-cs/Terminal.Gui/issues/3413 - if (Arrangement.HasFlag (ViewArrangement.Overlapped)) + if (IsLayoutNeeded ()) { - SetNeedsDisplay (); + Debug.WriteLine ($"Layout should be de-coupled from drawing: {this}"); } - if (!NeedsDisplay && !SubViewNeedsDisplay && !IsLayoutNeeded ()) + //// TODO: This ensures overlapped views are drawn correctly. However, this is inefficient. + //// TODO: The correct fix is to implement non-rectangular clip regions: https://github.com/gui-cs/Terminal.Gui/issues/3413 + //if ((this != Application.Top || this is Toplevel { Modal: true }) && Arrangement.HasFlag (ViewArrangement.Overlapped)) + //{ + // SetNeedsDisplay (); + //} + + if (!NeedsDisplay && !SubViewNeedsDisplay) { return; } - OnDrawAdornments (); + if (NeedsDisplay) + { + OnDrawAdornments (); + } if (ColorScheme is { }) { @@ -526,7 +534,7 @@ public virtual void OnDrawContent (Rectangle viewport) } // BUGBUG: this clears way too frequently. Need to optimize this. - if (SuperView is { } || Arrangement.HasFlag (ViewArrangement.Overlapped)) + if (NeedsDisplay/* || Arrangement.HasFlag (ViewArrangement.Overlapped)*/) { Clear (); } @@ -558,23 +566,22 @@ public virtual void OnDrawContent (Rectangle viewport) view => view.Visible && (view.NeedsDisplay || view.SubViewNeedsDisplay - || view.IsLayoutNeeded () - || view.Arrangement.HasFlag (ViewArrangement.Overlapped) + // || view.Arrangement.HasFlag (ViewArrangement.Overlapped) )); foreach (View view in subviewsNeedingDraw) { if (view.IsLayoutNeeded ()) { - Debug.WriteLine ("Layout should be de-coupled from drawing"); - view.LayoutSubviews (); + Debug.WriteLine ($"Layout should be de-coupled from drawing: {view}"); + //view.LayoutSubviews (); } // TODO: This ensures overlapped views are drawn correctly. However, this is inefficient. // TODO: The correct fix is to implement non-rectangular clip regions: https://github.com/gui-cs/Terminal.Gui/issues/3413 if (view.Arrangement.HasFlag (ViewArrangement.Overlapped)) { - view.SetNeedsDisplay (); + // view.SetNeedsDisplay (); } view.Draw (); diff --git a/Terminal.Gui/View/View.Hierarchy.cs b/Terminal.Gui/View/View.Hierarchy.cs index 0b493d2a1a..15bb745db1 100644 --- a/Terminal.Gui/View/View.Hierarchy.cs +++ b/Terminal.Gui/View/View.Hierarchy.cs @@ -85,8 +85,15 @@ public virtual View Add (View view) view.EndInit (); } - CheckDimAuto (); - view.SetRelativeLayout(GetContentSize()); + + SetLayoutNeeded(); + //CheckDimAuto (); + // view.SetRelativeLayout(Application.Screen.Size); + + //if (view.Visible) + //{ + // SetNeedsDisplay(); + //}; return view; } diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs index 5422bdf832..d9108eaefe 100644 --- a/Terminal.Gui/View/View.Layout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -197,7 +197,14 @@ out int ny /// public Rectangle Frame { - get => _frame; + get + { + if (_layoutNeeded) + { + //Debug.WriteLine("Frame_get with _layoutNeeded"); + } + return _frame; + } set { if (_frame == value) @@ -346,7 +353,7 @@ public Pos X if (!IsInitialized) { // Supports Unit Tests that don't call Begin/EndInit - SetRelativeLayout (GetBestGuessSuperViewContentSize ()); + //SetRelativeLayout (GetBestGuessSuperViewContentSize ()); } SetLayoutNeeded (); @@ -395,7 +402,7 @@ public Pos Y if (!IsInitialized) { // Supports Unit Tests that don't call Begin/EndInit - SetRelativeLayout (GetBestGuessSuperViewContentSize ()); + //SetRelativeLayout (GetBestGuessSuperViewContentSize ()); } SetLayoutNeeded (); @@ -454,7 +461,7 @@ public Dim? Height if (!IsInitialized) { // Supports Unit Tests that don't call Begin/EndInit - SetRelativeLayout (GetBestGuessSuperViewContentSize ()); + //SetRelativeLayout (GetBestGuessSuperViewContentSize ()); } SetLayoutNeeded (); @@ -513,7 +520,7 @@ public Dim? Width if (!IsInitialized) { // Supports Unit Tests that don't call Begin/EndInit - SetRelativeLayout (GetBestGuessSuperViewContentSize ()); + //SetRelativeLayout (GetBestGuessSuperViewContentSize ()); } SetLayoutNeeded (); @@ -562,7 +569,8 @@ public Dim? Width /// /// The size of the SuperView's content (nominally the same as this.SuperView.GetContentSize ()). /// - internal void SetRelativeLayout (Size superviewContentSize) + /// if successful. means a dependent View still needs layout. + public bool SetRelativeLayout (Size superviewContentSize) { Debug.Assert (_x is { }); Debug.Assert (_y is { }); @@ -571,31 +579,49 @@ internal void SetRelativeLayout (Size superviewContentSize) CheckDimAuto (); SetTextFormatterSize (); - int newX, newW, newY, newH; - // Calculate the new X, Y, Width, and Height - // If the Width or Height is Dim.Auto, calculate the Width or Height first. Otherwise, calculate the X or Y first. - if (_width is DimAuto) - { - newW = _width.Calculate (0, superviewContentSize.Width, this, Dimension.Width); - newX = _x.Calculate (superviewContentSize.Width, newW, this, Dimension.Width); - } - else + try { - newX = _x.Calculate (superviewContentSize.Width, _width, this, Dimension.Width); - newW = _width.Calculate (newX, superviewContentSize.Width, this, Dimension.Width); - } + // Calculate the new X, Y, Width, and Height + // If the Width or Height is Dim.Auto, calculate the Width or Height first. Otherwise, calculate the X or Y first. + if (_width.Has (out _)) + { + newW = _width.Calculate (0, superviewContentSize.Width, this, Dimension.Width); + newX = _x.Calculate (superviewContentSize.Width, newW, this, Dimension.Width); + if (newW != Frame.Width) + { + // Pos.Calculate gave us a new position. We need to redo dimension + newW = _width.Calculate (newX, superviewContentSize.Width, this, Dimension.Width); + } + } + else + { + newX = _x.Calculate (superviewContentSize.Width, _width, this, Dimension.Width); + newW = _width.Calculate (newX, superviewContentSize.Width, this, Dimension.Width); + } + + if (_height.Has (out _)) + { + newH = _height.Calculate (0, superviewContentSize.Height, this, Dimension.Height); + newY = _y.Calculate (superviewContentSize.Height, newH, this, Dimension.Height); + if (newH != Frame.Height) + { + // Pos.Calculate gave us a new position. We need to redo dimension + newH = _height.Calculate (newY, superviewContentSize.Height, this, Dimension.Height); + } + } + else + { + newY = _y.Calculate (superviewContentSize.Height, _height, this, Dimension.Height); + newH = _height.Calculate (newY, superviewContentSize.Height, this, Dimension.Height); + } - if (_height is DimAuto) - { - newH = _height.Calculate (0, superviewContentSize.Height, this, Dimension.Height); - newY = _y.Calculate (superviewContentSize.Height, newH, this, Dimension.Height); } - else + catch (Exception e) { - newY = _y.Calculate (superviewContentSize.Height, _height, this, Dimension.Height); - newH = _height.Calculate (newY, superviewContentSize.Height, this, Dimension.Height); + // A Dim/PosFunc threw indicating it could not calculate (typically because a dependent View was not laid out). + return false; } Rectangle newFrame = new (newX, newY, newW, newH); @@ -630,6 +656,9 @@ internal void SetRelativeLayout (Size superviewContentSize) { SetTitleTextFormatterSize (); } + + SetNeedsDisplay (); + SuperView?.SetNeedsDisplay (); } if (TextFormatter.ConstrainToWidth is null) @@ -641,6 +670,8 @@ internal void SetRelativeLayout (Size superviewContentSize) { TextFormatter.ConstrainToHeight = GetContentSize ().Height; } + + return true; } // TODO: remove virtual from this @@ -686,9 +717,22 @@ public virtual void LayoutSubviews () CollectAll (this, ref nodes, ref edges); List ordered = TopologicalSort (SuperView!, nodes, edges); + List redo = new (); foreach (View v in ordered) { - LayoutSubview (v, contentSize); + if (!LayoutSubview (v, contentSize)) + { + redo.Add (v); + } + } + + bool layoutStillNeeded = false; + foreach (View v in redo) + { + if (!LayoutSubview (v, contentSize)) + { + layoutStillNeeded = true; + } } // If the 'to' is rooted to 'from' it's a special-case. @@ -697,20 +741,33 @@ public virtual void LayoutSubviews () { foreach ((View from, View to) in edges) { + Debug.Fail ("This is dead code?"); LayoutSubview (to, from.GetContentSize ()); } } - _layoutNeeded = false; + _layoutNeeded = layoutStillNeeded; OnLayoutComplete (new (contentSize)); } - private void LayoutSubview (View v, Size contentSize) + /// + /// Lays out . + /// + /// + /// + /// If the view could not be laid out (typically because a dependencies was not ready). + private bool LayoutSubview (View subView, Size contentSize) { // Note, SetRelativeLayout calls SetTextFormatterSize - v.SetRelativeLayout (contentSize); - v.LayoutSubviews (); + if (subView.SetRelativeLayout (contentSize)) + { + subView.LayoutSubviews (); + + return true; + } + + return false; } /// @@ -735,13 +792,13 @@ private void LayoutSubview (View v, Size contentSize) /// layout. /// /// - internal bool IsLayoutNeeded () => _layoutNeeded; + public bool IsLayoutNeeded () => _layoutNeeded; /// /// INTERNAL API - Sets for this View and all of it's subviews and it's SuperView. /// The main loop will call SetRelativeLayout and LayoutSubviews for any view with set. /// - internal void SetLayoutNeeded () + public void SetLayoutNeeded () { if (IsLayoutNeeded ()) { @@ -751,6 +808,10 @@ internal void SetLayoutNeeded () _layoutNeeded = true; + Margin?.SetLayoutNeeded (); + Border?.SetLayoutNeeded (); + Padding?.SetLayoutNeeded (); + // Use a stack to avoid recursion Stack stack = new Stack (Subviews); @@ -769,13 +830,12 @@ internal void SetLayoutNeeded () TextFormatter.NeedsFormat = true; - // Use a loop to avoid recursion for superview hierarchy - View? superView = SuperView; - while (superView != null && !superView.IsLayoutNeeded ()) + if (this is Adornment adornment) { - superView._layoutNeeded = true; - superView = superView.SuperView; + adornment.Parent?.SetLayoutNeeded (); } + + SuperView?.SetLayoutNeeded (); } /// @@ -827,25 +887,25 @@ internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View /// internal void CollectDim (Dim? dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) { - switch (dim) + if (dim!.Has (out var dv)) { - case DimView dv: - // See #2461 - //if (!from.InternalSubviews.Contains (dv.Target)) { - // throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}"); - //} - if (dv.Target != this) - { - nEdges.Add ((dv.Target!, from)); - } + if (dv.Target != this) + { + nEdges.Add ((dv.Target!, from)); + } + } - return; - case DimCombine dc: - CollectDim (dc.Left, from, ref nNodes, ref nEdges); - CollectDim (dc.Right, from, ref nNodes, ref nEdges); + if (dim!.Has (out var dc)) + { + CollectDim (dc.Left, from, ref nNodes, ref nEdges); + CollectDim (dc.Right, from, ref nNodes, ref nEdges); + } + + if (dim!.Has (out var df)) + { - break; } + } /// @@ -863,10 +923,7 @@ internal void CollectPos (Pos pos, View from, ref HashSet nNodes, ref Hash switch (pos) { case PosView pv: - // See #2461 - //if (!from.InternalSubviews.Contains (pv.Target)) { - // throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}"); - //} + Debug.Assert (pv.Target is { }); if (pv.Target != this) { nEdges.Add ((pv.Target!, from)); diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 0df94b488b..0aa1745a9c 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -363,7 +363,10 @@ public virtual bool Visible OnVisibleChanged (); VisibleChanged?.Invoke (this, EventArgs.Empty); + SetLayoutNeeded (); + SuperView?.SetLayoutNeeded(); SetNeedsDisplay (); + SuperView?.SetNeedsDisplay(); } } diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index cf9108c1b5..21b483cba4 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -31,7 +31,7 @@ public Bar (IEnumerable shortcuts) _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e); _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e); - Initialized += Bar_Initialized; + // Initialized += Bar_Initialized; MouseEvent += OnMouseEvent; if (shortcuts is null) @@ -77,8 +77,10 @@ private void OnMouseEvent (object? sender, MouseEventArgs e) } } - private void Bar_Initialized (object? sender, EventArgs e) + /// + public override void EndInit () { + base.EndInit (); ColorScheme = Colors.ColorSchemes ["Menu"]; LayoutBarItems (GetContentSize ()); } @@ -199,6 +201,9 @@ internal override void OnLayoutStarted (LayoutEventArgs args) LayoutBarItems (args.OldContentSize); } + // This is used to calculate the minimum width of the Bar when the width is NOT Dim.Auto + private int? _minimumDimAutoWidth; + private void LayoutBarItems (Size contentSize) { View? prevBarItem = null; @@ -229,7 +234,7 @@ private void LayoutBarItems (Size contentSize) minKeyWidth = int.Max (minKeyWidth, shortcut.KeyView.Text.GetColumns ()); } - var maxBarItemWidth = 0; + var _maxBarItemWidth = 0; var totalHeight = 0; for (var index = 0; index < Subviews.Count; index++) @@ -246,7 +251,8 @@ private void LayoutBarItems (Size contentSize) if (barItem is Shortcut scBarItem) { scBarItem.MinimumKeyTextSize = minKeyWidth; - maxBarItemWidth = Math.Max (maxBarItemWidth, scBarItem.Frame.Width); + + _maxBarItemWidth = Math.Max (_maxBarItemWidth, scBarItem.Frame.Width); } if (prevBarItem == null) @@ -268,7 +274,7 @@ private void LayoutBarItems (Size contentSize) foreach (View barItem in Subviews) { - barItem.Width = maxBarItemWidth; + barItem.Width = _maxBarItemWidth; } Height = Dim.Auto (DimAutoStyle.Content, totalHeight); diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index a8c50755b3..7b6532a9fa 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -70,7 +70,9 @@ public override void BeginInit () { base.BeginInit (); - Frame = MakeFrame (Frame.X, Frame.Y, _barItems!.Children!, Parent); + var frame = MakeFrame (Frame.X, Frame.Y, _barItems!.Children!, Parent); + Width = frame.Width; + Height = frame.Height; if (_barItems.Children is { }) { diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index a15d1e4633..71e042be1b 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -861,6 +861,7 @@ internal void OpenMenu (int index, int sIndex = -1, MenuBarItem? subMenu = null! if (Application.Top is { }) { Application.Top.Add (_openMenu); + // _openMenu.SetRelativeLayout (Application.Screen.Size); } else { diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index ce1e0e8ac6..4ac1e66154 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -109,6 +109,7 @@ public Shortcut (Key key, string? commandText, Action? action, string? helpText CommandView = new () { + Id = "CommandView", Width = Dim.Auto (), Height = Dim.Auto (DimAutoStyle.Auto, 1) }; @@ -116,14 +117,11 @@ public Shortcut (Key key, string? commandText, Action? action, string? helpText HelpView.Id = "_helpView"; HelpView.CanFocus = false; HelpView.Text = helpText ?? string.Empty; - Add (HelpView); KeyView.Id = "_keyView"; KeyView.CanFocus = false; - Add (KeyView); LayoutStarted += OnLayoutStarted; - Initialized += OnInitialized; key ??= Key.Empty; Key = key; @@ -132,41 +130,58 @@ public Shortcut (Key key, string? commandText, Action? action, string? helpText return; - void OnInitialized (object? sender, EventArgs e) - { - SuperViewRendersLineCanvas = true; - Border.Settings &= ~BorderSettings.Title; + } - ShowHide (); + // Helper to set Width consistently + private Dim GetWidthDimAuto () + { + // TODO: PosAlign.CalculateMinDimension is a hack. Need to figure out a better way of doing this. + return Dim.Auto ( + DimAutoStyle.Content, + Dim.Func (() => + { + if (Subviews [0].IsLayoutNeeded ()) + { + // throw new Exception (); + } + return PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width); + }), + Dim.Func (() => + { + return PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width); + }))!; + } - // Force Width to DimAuto to calculate natural width and then set it back - Dim savedDim = Width; - Width = GetWidthDimAuto (); - _minimumDimAutoWidth = Frame.Width; - Width = savedDim; + /// + public override void EndInit () + { + base.EndInit (); - SetCommandViewDefaultLayout (); - SetHelpViewDefaultLayout (); - SetKeyViewDefaultLayout (); + SuperViewRendersLineCanvas = true; + Border.Settings &= ~BorderSettings.Title; - SetColors (); - } + ShowHide (); - // Helper to set Width consistently - Dim GetWidthDimAuto () - { - // TODO: PosAlign.CalculateMinDimension is a hack. Need to figure out a better way of doing this. - return Dim.Auto ( - DimAutoStyle.Content, - Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width)), - Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width)))!; - } + // Force Width to DimAuto to calculate natural width and then set it back + Dim? savedDim = Width; + Width = GetWidthDimAuto (); + // Force a SRL) + SetRelativeLayout (Application.Screen.Size); + _minimumNatrualWidth = Frame.Width; + Width = savedDim; + + SetCommandViewDefaultLayout (); + SetHelpViewDefaultLayout (); + SetKeyViewDefaultLayout (); + + SetColors (); } private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; // This is used to calculate the minimum width of the Shortcut when the width is NOT Dim.Auto - private int? _minimumDimAutoWidth; + // It is calculated by setting Width to DimAuto temporarily and forcing layout + private int? _minimumNatrualWidth; /// protected override bool OnHighlight (CancelEventArgs args) @@ -238,7 +253,7 @@ private void OnLayoutStarted (object? sender, LayoutEventArgs e) { if (Width is DimAuto widthAuto) { - _minimumDimAutoWidth = Frame.Width; + _minimumNatrualWidth = Frame.Width; } else { @@ -256,9 +271,9 @@ private void OnLayoutStarted (object? sender, LayoutEventArgs e) // When Vertical, Command is first, then Help, then Key. // BUGBUG: This does not do what the above says. // TODO: Add Unit tests for this. - if (currentWidth < _minimumDimAutoWidth) + if (currentWidth < _minimumNatrualWidth) { - int delta = _minimumDimAutoWidth.Value - currentWidth; + int delta = _minimumNatrualWidth.Value - currentWidth; int maxHelpWidth = int.Max (0, HelpView.Text.GetColumns () + Margin.Thickness.Horizontal - delta); switch (maxHelpWidth) diff --git a/UICatalog/Scenarios/Navigation.cs b/UICatalog/Scenarios/Navigation.cs index d2c0c27a67..c3913af35c 100644 --- a/UICatalog/Scenarios/Navigation.cs +++ b/UICatalog/Scenarios/Navigation.cs @@ -57,6 +57,7 @@ public override void Main () Y = 0, Title = $"TopButton _{GetNextHotKey ()}" }; + button.Accepting += (sender, args) => MessageBox.Query ("hi", button.Title, "_Ok"); testFrame.Add (button); @@ -98,9 +99,10 @@ public override void Main () progressBar.Fraction += 0.01f; - Application.Wakeup (); + //Application.Wakeup (); - progressBar.SetNeedsDisplay (); + Application.Invoke (() => progressBar.SetNeedsDisplay ()); + ; }; timer.Start (); diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 63dc511fc2..4a0b42514d 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -387,7 +387,7 @@ private static void VerifyObjectsWereDisposed () // 'app' closed cleanly. foreach (Responder? inst in Responder.Instances) { - + Debug.Assert (inst.WasDisposed); } @@ -492,7 +492,6 @@ public UICatalogTopLevel () ) ] }; - Add (menuBar); _statusBar = new () { @@ -500,9 +499,7 @@ public UICatalogTopLevel () AlignmentModes = AlignmentModes.IgnoreFirstOrLast, CanFocus = false }; - _statusBar.Height = _statusBar.Visible ? Dim.Auto () : 0; - - Add (_statusBar); + _statusBar.Height = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: Dim.Func (() => _statusBar.Visible ? 1 : 0), maximumContentDim: Dim.Func (() => _statusBar.Visible ? 1 : 0)); ShVersion = new () { @@ -561,9 +558,16 @@ public UICatalogTopLevel () CategoryList = new () { X = 0, - Y = Pos.Bottom (MenuBar), + Y = Pos.Bottom (menuBar), Width = Dim.Auto (), - Height = Dim.Fill (Dim.Func (() => _statusBar.Frame.Height)), + Height = Dim.Fill (Dim.Func (() => + { + if (_statusBar.IsLayoutNeeded ()) + { + // throw new Exception ("DimFunc.Fn aborted because dependent View needs layout."); + } + return _statusBar.Frame.Height; + })), AllowsMarking = false, CanFocus = true, Title = "_Categories", @@ -580,9 +584,16 @@ public UICatalogTopLevel () ScenarioList = new () { X = Pos.Right (CategoryList) - 1, - Y = Pos.Bottom (MenuBar), + Y = Pos.Bottom (menuBar), Width = Dim.Fill (), - Height = Dim.Fill (Dim.Func (() => _statusBar.Frame.Height)), + Height = Dim.Fill (Dim.Func (() => + { + if (_statusBar.IsLayoutNeeded ()) + { + // throw new Exception ("DimFunc.Fn aborted because dependent View needs layout."); + } + return _statusBar.Frame.Height; + })), //AllowsMarking = false, CanFocus = true, Title = "_Scenarios", @@ -620,29 +631,6 @@ public UICatalogTopLevel () new () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName } ); ScenarioList.Style.ColumnStyles.Add (1, new () { MaxWidth = 1 }); - - //// Enable user to find & select a scenario by typing text - //// TableView does not (currently) have built-in CollectionNavigator support (the ability for the - //// user to type and the items that match get selected). We implement it in the app instead. - //ScenarioList.KeyDown += (s, a) => - // { - // if (CollectionNavigatorBase.IsCompatibleKey (a)) - // { - // int? newItem = - // _scenarioCollectionNav?.GetNextMatchingItem ( - // ScenarioList.SelectedRow, - // (char)a - // ); - - // if (newItem is int v && newItem != -1) - // { - // ScenarioList.SelectedRow = v; - // ScenarioList.EnsureSelectedCellIsVisible (); - // ScenarioList.SetNeedsDisplay (); - // a.Handled = true; - // } - // } - // }; ScenarioList.CellActivated += ScenarioView_OpenSelectedItem; // TableView typically is a grid where nav keys are biased for moving left/right. @@ -657,8 +645,10 @@ public UICatalogTopLevel () ScenarioList.MultiSelect = false; ScenarioList.KeyBindings.Remove (Key.A.WithCtrl); + Add (menuBar); Add (CategoryList); Add (ScenarioList); + Add (_statusBar); Loaded += LoadedHandler; Unloaded += UnloadedHandler; @@ -1097,7 +1087,6 @@ private void LoadedHandler (object? sender, EventArgs? args) _statusBar.VisibleChanged += (s, e) => { ShowStatusBar = _statusBar.Visible; - _statusBar.Height = _statusBar.Visible ? Dim.Auto () : 0; }; } From f5ddf6b584af7cc7222ec88d8ae04ee5d35000cf Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 16 Oct 2024 17:24:36 -0600 Subject: [PATCH 003/118] More prototyping 2 --- Terminal.Gui/Application/Application.Run.cs | 2 +- Terminal.Gui/View/View.Drawing.cs | 4 ++-- Terminal.Gui/View/View.Hierarchy.cs | 8 -------- Terminal.Gui/View/View.Layout.cs | 6 +++++- Terminal.Gui/View/View.cs | 4 ++++ Terminal.Gui/Views/Bar.cs | 3 +-- UICatalog/Scenarios/AllViewsTester.cs | 5 ++--- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index c268047432..ee980a37fa 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -514,7 +514,7 @@ public static void Refresh () { if (clear) { - tl.SetNeedsDisplay(); + tl.SetNeedsDisplay(Screen); } tl.Draw (); } diff --git a/Terminal.Gui/View/View.Drawing.cs b/Terminal.Gui/View/View.Drawing.cs index 6635f79c1a..9099c406a5 100644 --- a/Terminal.Gui/View/View.Drawing.cs +++ b/Terminal.Gui/View/View.Drawing.cs @@ -220,7 +220,7 @@ public void Draw () if (IsLayoutNeeded ()) { - Debug.WriteLine ($"Layout should be de-coupled from drawing: {this}"); + //Debug.WriteLine ($"Layout should be de-coupled from drawing: {this}"); } //// TODO: This ensures overlapped views are drawn correctly. However, this is inefficient. @@ -573,7 +573,7 @@ public virtual void OnDrawContent (Rectangle viewport) { if (view.IsLayoutNeeded ()) { - Debug.WriteLine ($"Layout should be de-coupled from drawing: {view}"); + //Debug.WriteLine ($"Layout should be de-coupled from drawing: {view}"); //view.LayoutSubviews (); } diff --git a/Terminal.Gui/View/View.Hierarchy.cs b/Terminal.Gui/View/View.Hierarchy.cs index 15bb745db1..2334d2b12f 100644 --- a/Terminal.Gui/View/View.Hierarchy.cs +++ b/Terminal.Gui/View/View.Hierarchy.cs @@ -85,15 +85,7 @@ public virtual View Add (View view) view.EndInit (); } - SetLayoutNeeded(); - //CheckDimAuto (); - // view.SetRelativeLayout(Application.Screen.Size); - - //if (view.Visible) - //{ - // SetNeedsDisplay(); - //}; return view; } diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs index d9108eaefe..d143cf10be 100644 --- a/Terminal.Gui/View/View.Layout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -657,7 +657,7 @@ public bool SetRelativeLayout (Size superviewContentSize) SetTitleTextFormatterSize (); } - SetNeedsDisplay (); + //SetNeedsDisplay (); SuperView?.SetNeedsDisplay (); } @@ -821,6 +821,10 @@ public void SetLayoutNeeded () if (!current.IsLayoutNeeded ()) { current._layoutNeeded = true; + current.Margin?.SetLayoutNeeded (); + current.Border?.SetLayoutNeeded (); + current.Padding?.SetLayoutNeeded (); + foreach (View subview in current.Subviews) { stack.Push (subview); diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 0aa1745a9c..ef0ecbe213 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -229,6 +229,7 @@ public virtual void EndInit () // These calls were moved from BeginInit as they access Viewport which is indeterminate until EndInit is called. UpdateTextDirection (TextDirection); UpdateTextFormatterText (); + SetLayoutNeeded (); if (_subviews is { }) @@ -242,6 +243,9 @@ public virtual void EndInit () } } + // TOOD: Figure out how to move this out of here and just depend on IsLayoutNeeded in Mainloop + LayoutSubviews(); + Initialized?.Invoke (this, EventArgs.Empty); } diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index 21b483cba4..be81134e2b 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -82,7 +82,6 @@ public override void EndInit () { base.EndInit (); ColorScheme = Colors.ColorSchemes ["Menu"]; - LayoutBarItems (GetContentSize ()); } /// @@ -121,7 +120,7 @@ public Orientation Orientation /// public void OnOrientationChanged (Orientation newOrientation) { - SetLayoutNeeded (); + LayoutBarItems (SuperView?.GetContentSize() ?? Application.Screen.Size); } #endregion diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index 026a183836..0392ee7a19 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -384,7 +384,6 @@ private void CreateCurrentView (Type type) _curView = view; _hostPane.Add (_curView); - // Application.Refresh(); } private void DisposeCurrentView () @@ -558,12 +557,12 @@ private void CurrentView_Initialized (object sender, EventArgs e) return; } - if (!view.Width!.Has (out _) || (view.Width is null || view.Frame.Width == 0)) + if (!view.Width!.Has (out _) || (view.Width is null)) { view.Width = Dim.Fill (); } - if (!view.Height!.Has (out _) || (view.Height is null || view.Frame.Height == 0)) + if (!view.Height!.Has (out _) || (view.Height is null)) { view.Height = Dim.Fill (); } From 8c7982f9c03eb4f34aa2aceb96b6df3dce763a6e Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 17 Oct 2024 10:39:56 -0600 Subject: [PATCH 004/118] Tons of Layout refactoring. LayoutSubviews is now internal. --- Terminal.Gui/Application/Application.Run.cs | 37 +- .../Application/Application.Screen.cs | 22 +- Terminal.Gui/View/Adornment/Adornment.cs | 9 +- Terminal.Gui/View/Adornment/Border.cs | 30 +- Terminal.Gui/View/Adornment/Margin.cs | 8 +- Terminal.Gui/View/Layout/PosAlign.cs | 2 +- Terminal.Gui/View/View.Adornments.cs | 10 +- Terminal.Gui/View/View.Content.cs | 2 +- Terminal.Gui/View/View.Layout.cs | 133 +++--- Terminal.Gui/View/View.cs | 8 +- Terminal.Gui/Views/ColorPicker.cs | 7 +- Terminal.Gui/Views/FileDialog.cs | 5 +- Terminal.Gui/Views/ScrollBarView.cs | 4 +- Terminal.Gui/Views/TabView.cs | 47 +- Terminal.Gui/Views/TileView.cs | 74 ++- Terminal.Gui/Views/Toplevel.cs | 30 +- Terminal.Gui/Views/Wizard/Wizard.cs | 3 - UICatalog/Scenarios/BorderEditor.cs | 6 +- UICatalog/Scenarios/ColorPicker.cs | 1 - UICatalog/Scenarios/ComputedLayout.cs | 10 - UICatalog/Scenarios/ConfigurationEditor.cs | 2 - UICatalog/Scenarios/Dialogs.cs | 1 - UICatalog/Scenarios/Editor.cs | 3 - UICatalog/Scenarios/LineDrawing.cs | 7 +- UICatalog/Scenarios/PosAlignDemo.cs | 2 - UICatalog/Scenarios/Progress.cs | 2 - UICatalog/Scenarios/Scrolling.cs | 4 +- UICatalog/Scenarios/Sliders.cs | 8 - UICatalog/Scenarios/TileViewNesting.cs | 13 +- UICatalog/Scenarios/Wizards.cs | 3 - UnitTests/Application/ApplicationTests.cs | 2 +- UnitTests/Dialogs/DialogTests.cs | 23 +- UnitTests/Dialogs/WizardTests.cs | 10 +- UnitTests/View/Adornment/BorderTests.cs | 17 +- UnitTests/View/DrawTests.cs | 313 +++++++++--- UnitTests/View/Layout/AbsoluteLayoutTests.cs | 236 --------- UnitTests/View/Layout/Dim.AutoTests.cs | 24 +- UnitTests/View/Layout/LayoutTests.cs | 451 ++++++++++++++++-- UnitTests/View/Layout/Pos.CenterTests.cs | 4 +- UnitTests/View/Layout/ToScreenTests.cs | 3 +- UnitTests/View/Mouse/MouseTests.cs | 5 +- UnitTests/View/ViewTests.cs | 2 +- UnitTests/Views/ContextMenuTests.cs | 16 +- UnitTests/Views/MenuBarTests.cs | 20 +- UnitTests/Views/TextFieldTests.cs | 3 +- UnitTests/Views/TextViewTests.cs | 3 +- UnitTests/Views/ToplevelTests.cs | 4 +- 47 files changed, 928 insertions(+), 701 deletions(-) delete mode 100644 UnitTests/View/Layout/AbsoluteLayoutTests.cs diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index ee980a37fa..fa35743f89 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -183,12 +183,8 @@ public static RunState Begin (Toplevel toplevel) toplevel.BeginInit (); toplevel.EndInit (); - if (toplevel.SetRelativeLayout (Driver!.Screen.Size)) - { - toplevel.LayoutSubviews (); - } - - // toplevel.SetLayoutNeeded(); + // Force a layout - normally this is done each iteration of the main loop but we prime it here. + toplevel.Layout (Screen.Size); } // Try to set initial focus to any TabStop @@ -197,6 +193,11 @@ public static RunState Begin (Toplevel toplevel) toplevel.SetFocus (); } + // DEBATE: Should Begin call Refresh (or Draw) here? It previously did. + // FOR: the screen has something on it after Begin is called. + // AGAINST: the screen is cleared and then redrawn in RunLoop. We don't want to draw twice. + Refresh(); + toplevel.OnLoaded (); if (PositionCursor ()) @@ -227,11 +228,12 @@ internal static bool PositionCursor () // If the view is not visible or enabled, don't position the cursor if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled) { - Driver!.GetCursorVisibility (out CursorVisibility current); + CursorVisibility current = CursorVisibility.Invisible; + Driver?.GetCursorVisibility (out current); if (current != CursorVisibility.Invisible) { - Driver.SetCursorVisibility (CursorVisibility.Invisible); + Driver?.SetCursorVisibility (CursorVisibility.Invisible); } return false; @@ -497,11 +499,7 @@ public static void Refresh () if (tl.IsLayoutNeeded ()) { clear = true; - - if (tl.SetRelativeLayout (Screen.Size)) - { - tl.LayoutSubviews (); - } + tl.Layout (Screen.Size); } } @@ -554,23 +552,23 @@ public static void RunLoop (RunState state) return; } - RunIteration (ref state, ref firstIteration); + firstIteration = RunIteration (ref state, firstIteration); } MainLoop!.Running = false; // Run one last iteration to consume any outstanding input events from Driver // This is important for remaining OnKeyUp events. - RunIteration (ref state, ref firstIteration); + RunIteration (ref state, firstIteration); } /// Run one application iteration. /// The state returned by . /// - /// Set to if this is the first run loop iteration. Upon return, it - /// will be set to if at least one iteration happened. + /// Set to if this is the first run loop iteration. /// - public static void RunIteration (ref RunState state, ref bool firstIteration) + /// if at least one iteration happened. + public static bool RunIteration (ref RunState state, bool firstIteration = false) { if (MainLoop!.Running && MainLoop.EventsPending ()) { @@ -588,7 +586,7 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) if (Top is null) { - return; + return firstIteration; } Refresh (); @@ -598,6 +596,7 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) Driver!.UpdateCursor (); } + return firstIteration; } /// Stops the provided , causing or the if provided. diff --git a/Terminal.Gui/Application/Application.Screen.cs b/Terminal.Gui/Application/Application.Screen.cs index 940b1b22c4..bc5a3c5974 100644 --- a/Terminal.Gui/Application/Application.Screen.cs +++ b/Terminal.Gui/Application/Application.Screen.cs @@ -3,13 +3,28 @@ namespace Terminal.Gui; public static partial class Application // Screen related stuff { + private static Size _screenSize = new (2048, 2048); + + /// + /// INTERNAL API for Unit Tests. Only works if there's no driver. + /// + /// + internal static void SetScreenSize (Size size) + { + if (Driver is { }) + { + throw new InvalidOperationException ("Cannot set the screen size when the ConsoleDriver is already initialized."); + } + _screenSize = size; + } + /// /// Gets the size of the screen. This is the size of the screen as reported by the . /// /// /// If the has not been initialized, this will return a default size of 2048x2048; useful for unit tests. /// - public static Rectangle Screen => Driver?.Screen ?? new (0, 0, 2048, 2048); + public static Rectangle Screen => Driver?.Screen ?? new (new (0, 0), _screenSize); /// Invoked when the terminal's size changed. The new size of the terminal is provided. /// @@ -35,11 +50,8 @@ public static bool OnSizeChanging (SizeChangedEventArgs args) foreach (Toplevel t in TopLevels) { - if (t.SetRelativeLayout (args.Size.Value)) - { - t.LayoutSubviews (); - } t.OnSizeChanging (new (args.Size)); + t.SetLayoutNeeded (); } Refresh (); diff --git a/Terminal.Gui/View/Adornment/Adornment.cs b/Terminal.Gui/View/Adornment/Adornment.cs index 9d3559f63b..7a01a27f44 100644 --- a/Terminal.Gui/View/Adornment/Adornment.cs +++ b/Terminal.Gui/View/Adornment/Adornment.cs @@ -152,18 +152,15 @@ public override void OnDrawContent (Rectangle viewport) Rectangle screen = ViewportToScreen (viewport); Attribute normalAttr = GetNormalColor (); - Driver.SetAttribute (normalAttr); + Driver?.SetAttribute (normalAttr); // This just draws/clears the thickness, not the insides. Thickness.Draw (screen, ToString ()); if (!string.IsNullOrEmpty (TextFormatter.Text)) { - if (TextFormatter is { }) - { - TextFormatter.ConstrainToSize = Frame.Size; - TextFormatter.NeedsFormat = true; - } + TextFormatter.ConstrainToSize = Frame.Size; + TextFormatter.NeedsFormat = true; } TextFormatter?.Draw (screen, normalAttr, normalAttr, Rectangle.Empty); diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index 29eb50cbdd..9a36b583e4 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -703,15 +703,15 @@ public override void OnDrawContent (Rectangle viewport) bool drawBottom = Thickness.Bottom > 0 && Frame.Width > 1 && Frame.Height > 1; bool drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0); - Attribute prevAttr = Driver.GetAttribute (); + Attribute prevAttr = Driver?.GetAttribute () ?? Attribute.Default; if (ColorScheme is { }) { - Driver.SetAttribute (GetNormalColor ()); + Driver?.SetAttribute (GetNormalColor ()); } else { - Driver.SetAttribute (Parent!.GetNormalColor ()); + Driver?.SetAttribute (Parent!.GetNormalColor ()); } if (drawTop) @@ -726,7 +726,7 @@ public override void OnDrawContent (Rectangle viewport) borderBounds.Width, Orientation.Horizontal, lineStyle, - Driver.GetAttribute () + Driver?.GetAttribute () ); } else @@ -741,7 +741,7 @@ public override void OnDrawContent (Rectangle viewport) Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, lineStyle, - Driver.GetAttribute () + Driver?.GetAttribute () ); } @@ -755,7 +755,7 @@ public override void OnDrawContent (Rectangle viewport) Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, lineStyle, - Driver.GetAttribute () + Driver?.GetAttribute () ); lc?.AddLine ( @@ -763,7 +763,7 @@ public override void OnDrawContent (Rectangle viewport) Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, lineStyle, - Driver.GetAttribute () + Driver?.GetAttribute () ); } @@ -774,7 +774,7 @@ public override void OnDrawContent (Rectangle viewport) 2, Orientation.Horizontal, lineStyle, - Driver.GetAttribute () + Driver?.GetAttribute () ); // Add a vert line for ╔╡ @@ -783,7 +783,7 @@ public override void OnDrawContent (Rectangle viewport) titleBarsLength, Orientation.Vertical, LineStyle.Single, - Driver.GetAttribute () + Driver?.GetAttribute () ); // Add a vert line for ╞ @@ -798,7 +798,7 @@ public override void OnDrawContent (Rectangle viewport) titleBarsLength, Orientation.Vertical, LineStyle.Single, - Driver.GetAttribute () + Driver?.GetAttribute () ); // Add the right hand line for ╞═════╗ @@ -813,7 +813,7 @@ public override void OnDrawContent (Rectangle viewport) borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, lineStyle, - Driver.GetAttribute () + Driver?.GetAttribute () ); } } @@ -827,7 +827,7 @@ public override void OnDrawContent (Rectangle viewport) sideLineLength, Orientation.Vertical, lineStyle, - Driver.GetAttribute () + Driver?.GetAttribute () ); } #endif @@ -839,7 +839,7 @@ public override void OnDrawContent (Rectangle viewport) borderBounds.Width, Orientation.Horizontal, lineStyle, - Driver.GetAttribute () + Driver?.GetAttribute () ); } @@ -850,11 +850,11 @@ public override void OnDrawContent (Rectangle viewport) sideLineLength, Orientation.Vertical, lineStyle, - Driver.GetAttribute () + Driver?.GetAttribute () ); } - Driver.SetAttribute (prevAttr); + Driver?.SetAttribute (prevAttr); // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler if (Diagnostics.HasFlag (ViewDiagnosticFlags.Ruler)) diff --git a/Terminal.Gui/View/Adornment/Margin.cs b/Terminal.Gui/View/Adornment/Margin.cs index 46acbddbb8..eff603e296 100644 --- a/Terminal.Gui/View/Adornment/Margin.cs +++ b/Terminal.Gui/View/Adornment/Margin.cs @@ -117,16 +117,12 @@ public override void OnDrawContent (Rectangle viewport) { IEnumerable subviewsNeedingDraw = Subviews.Where ( view => view.Visible - && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.IsLayoutNeeded ()) + && (view.NeedsDisplay || view.SubViewNeedsDisplay) ); foreach (View view in subviewsNeedingDraw) { - if (view.IsLayoutNeeded ()) - { - view.LayoutSubviews (); - } - + //view.Layout (); view.Draw (); } } diff --git a/Terminal.Gui/View/Layout/PosAlign.cs b/Terminal.Gui/View/Layout/PosAlign.cs index 197621cf01..6d2360b1f1 100644 --- a/Terminal.Gui/View/Layout/PosAlign.cs +++ b/Terminal.Gui/View/Layout/PosAlign.cs @@ -10,7 +10,7 @@ namespace Terminal.Gui; /// /// /// Updating the properties of is supported, but will not automatically cause re-layout to -/// happen. +/// happen. /// must be called on the SuperView. /// /// diff --git a/Terminal.Gui/View/View.Adornments.cs b/Terminal.Gui/View/View.Adornments.cs index 54f110d2a6..c03e832d40 100644 --- a/Terminal.Gui/View/View.Adornments.cs +++ b/Terminal.Gui/View/View.Adornments.cs @@ -54,7 +54,7 @@ private void DisposeAdornments () /// /// /// Changing the size of an adornment (, , or ) will - /// change the size of and trigger to update the layout of the + /// change the size of which will call to update the layout of the /// and its . /// /// @@ -109,8 +109,8 @@ public virtual ShadowStyle ShadowStyle /// View's content and are not clipped by the View's Clip Area. /// /// - /// Changing the size of a frame (, , or ) will - /// change the size of the and trigger to update the layout of the + /// Changing the size of an adornment (, , or ) will + /// change the size of which will call to update the layout of the /// and its . /// /// @@ -217,8 +217,8 @@ public virtual void SetBorderStyle (LineStyle value) /// View's content and are not clipped by the View's Clip Area. /// /// - /// Changing the size of a frame (, , or ) will - /// change the size of the and trigger to update the layout of the + /// Changing the size of an adornment (, , or ) will + /// change the size of which will call to update the layout of the /// and its . /// /// diff --git a/Terminal.Gui/View/View.Content.cs b/Terminal.Gui/View/View.Content.cs index ba441f56f8..749c4725b2 100644 --- a/Terminal.Gui/View/View.Content.cs +++ b/Terminal.Gui/View/View.Content.cs @@ -265,7 +265,7 @@ public ViewportSettings ViewportSettings /// /// /// Altering the Viewport Size will eventually (when the view is next laid out) cause the - /// and methods to be called. + /// and methods to be called. /// /// public virtual Rectangle Viewport diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs index d143cf10be..af6dad65e5 100644 --- a/Terminal.Gui/View/View.Layout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -190,7 +190,7 @@ out int ny /// /// /// Altering the Frame will eventually (when the view hierarchy is next laid out via see - /// cref="LayoutSubviews"/>) cause and + /// cref="LayoutSubviews"/>) cause and /// /// methods to be called. /// @@ -207,21 +207,15 @@ public Rectangle Frame } set { - if (_frame == value) + // This will set _frame, call SetsNeedsLayout, and raise OnViewportChanged/ViewportChanged + if (SetFrame (value with { Width = Math.Max (value.Width, 0), Height = Math.Max (value.Height, 0) })) { - return; + // If Frame gets set, set all Pos/Dim to Absolute values. + _x = _frame.X; + _y = _frame.Y; + _width = _frame.Width; + _height = _frame.Height; } - - // This will set _frame, call SetsNeedsLayout, and raise OnViewportChanged/ViewportChanged - SetFrame (value with { Width = Math.Max (value.Width, 0), Height = Math.Max (value.Height, 0) }); - - // If Frame gets set, set all Pos/Dim to Absolute values. - _x = _frame.X; - _y = _frame.Y; - _width = _frame.Width; - _height = _frame.Height; - - SetLayoutNeeded (); } } @@ -229,11 +223,12 @@ public Rectangle Frame /// INTERNAL API - Sets _frame, calls SetsNeedsLayout, and raises OnViewportChanged/ViewportChanged /// /// - private void SetFrame (in Rectangle frame) + /// if the frame was changed. + private bool SetFrame (in Rectangle frame) { if (_frame == frame) { - return; + return false; } var oldViewport = Rectangle.Empty; @@ -252,6 +247,8 @@ private void SetFrame (in Rectangle frame) // BUGBUG: When SetFrame is called from Frame_set, this event gets raised BEFORE OnResizeNeeded. Is that OK? OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport)); + + return true; } /// Gets the with a screen-relative location. @@ -331,7 +328,7 @@ public virtual Point ScreenToFrame (in Point location) /// /// /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. + /// and methods to be called. /// /// /// Changing this property will cause to be updated. @@ -350,12 +347,6 @@ public Pos X _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null"); - if (!IsInitialized) - { - // Supports Unit Tests that don't call Begin/EndInit - //SetRelativeLayout (GetBestGuessSuperViewContentSize ()); - } - SetLayoutNeeded (); } } @@ -380,7 +371,7 @@ public Pos X /// /// /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. + /// and methods to be called. /// /// /// Changing this property will cause to be updated. @@ -399,12 +390,6 @@ public Pos Y _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null"); - if (!IsInitialized) - { - // Supports Unit Tests that don't call Begin/EndInit - //SetRelativeLayout (GetBestGuessSuperViewContentSize ()); - } - SetLayoutNeeded (); } } @@ -430,7 +415,7 @@ public Pos Y /// /// /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. + /// and methods to be called. /// /// /// Changing this property will cause to be updated. @@ -458,12 +443,6 @@ public Dim? Height // Reset TextFormatter - Will be recalculated in SetTextFormatterSize TextFormatter.ConstrainToHeight = null; - if (!IsInitialized) - { - // Supports Unit Tests that don't call Begin/EndInit - //SetRelativeLayout (GetBestGuessSuperViewContentSize ()); - } - SetLayoutNeeded (); } } @@ -489,7 +468,7 @@ public Dim? Height /// /// /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. + /// and methods to be called. /// /// /// Changing this property will cause to be updated. @@ -517,12 +496,6 @@ public Dim? Width // Reset TextFormatter - Will be recalculated in SetTextFormatterSize TextFormatter.ConstrainToWidth = null; - if (!IsInitialized) - { - // Supports Unit Tests that don't call Begin/EndInit - //SetRelativeLayout (GetBestGuessSuperViewContentSize ()); - } - SetLayoutNeeded (); } } @@ -559,7 +532,7 @@ public Dim? Width /// /// /// If any of the view's subviews have a position or dimension dependent on either or - /// other subviews, on + /// other subviews, on /// will be called for that subview. /// /// @@ -657,7 +630,6 @@ public bool SetRelativeLayout (Size superviewContentSize) SetTitleTextFormatterSize (); } - //SetNeedsDisplay (); SuperView?.SetNeedsDisplay (); } @@ -674,10 +646,8 @@ public bool SetRelativeLayout (Size superviewContentSize) return true; } - // TODO: remove virtual from this /// - /// Invoked when the dimensions of the view have changed, for example in response to the container view or terminal - /// resizing. + /// INTERNAL API - Causes the view's subviews and adornments to be laid out within the view's content areas. Assumes the view's relative layout has been set via . /// /// /// @@ -690,7 +660,7 @@ public bool SetRelativeLayout (Size superviewContentSize) /// /// Raises the event before it returns. /// - public virtual void LayoutSubviews () + internal void LayoutSubviews () { if (!IsInitialized) { @@ -707,6 +677,7 @@ public virtual void LayoutSubviews () Size contentSize = GetContentSize (); OnLayoutStarted (new (contentSize)); + // The Adornments already have their Frame's set by SetRelativeLayout so we call LayoutSubViews vs. Layout here. Margin?.LayoutSubviews (); Border?.LayoutSubviews (); Padding?.LayoutSubviews (); @@ -720,7 +691,7 @@ public virtual void LayoutSubviews () List redo = new (); foreach (View v in ordered) { - if (!LayoutSubview (v, contentSize)) + if (!v.Layout (contentSize)) { redo.Add (v); } @@ -729,7 +700,7 @@ public virtual void LayoutSubviews () bool layoutStillNeeded = false; foreach (View v in redo) { - if (!LayoutSubview (v, contentSize)) + if (!v.Layout (contentSize)) { layoutStillNeeded = true; } @@ -742,7 +713,7 @@ public virtual void LayoutSubviews () foreach ((View from, View to) in edges) { Debug.Fail ("This is dead code?"); - LayoutSubview (to, from.GetContentSize ()); + to.Layout (from.GetContentSize ()); } } @@ -752,17 +723,16 @@ public virtual void LayoutSubviews () } /// - /// Lays out . + /// Performs layout of the view and its subviews within the specified content size. /// - /// /// /// If the view could not be laid out (typically because a dependencies was not ready). - private bool LayoutSubview (View subView, Size contentSize) + public bool Layout (Size contentSize) { // Note, SetRelativeLayout calls SetTextFormatterSize - if (subView.SetRelativeLayout (contentSize)) + if (SetRelativeLayout (contentSize)) { - subView.LayoutSubviews (); + LayoutSubviews (); return true; } @@ -770,6 +740,15 @@ private bool LayoutSubview (View subView, Size contentSize) return false; } + /// + /// Performs layout of the view and its subviews using the content size of either the or . + /// + /// If the view could not be laid out (typically because a dependencies was not ready). + public bool Layout () + { + return Layout (GetBestGuessSuperViewContentSize ()); + } + /// /// Raises the event. Called from before all sub-views /// have been laid out. @@ -787,17 +766,26 @@ private bool LayoutSubview (View subView, Size contentSize) private bool _layoutNeeded = true; /// - /// Indicates the View's Frame or the relative layout of the View's subviews (including Adornments) have - /// changed since the last time the View was laid out. Used to prevent from needlessly computing - /// layout. + /// Indicates the View's Frame or the layout of the View's subviews (including Adornments) have + /// changed since the last time the View was laid out. /// - /// - public bool IsLayoutNeeded () => _layoutNeeded; + /// + /// Used to prevent from needlessly computing + /// layout. + /// + /// + /// if layout is needed. + public bool IsLayoutNeeded () { return _layoutNeeded; } /// - /// INTERNAL API - Sets for this View and all of it's subviews and it's SuperView. - /// The main loop will call SetRelativeLayout and LayoutSubviews for any view with set. + /// Sets to return , indicating this View and all of it's subviews (including adornments) need to be laid out in the next Application iteration. /// + /// + /// + /// The will cause to be called on the next so there is normally no reason to call see . + /// + /// + public void SetLayoutNeeded () { if (IsLayoutNeeded ()) @@ -880,7 +868,7 @@ internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View } /// - /// Collects dimension (where Width or Height is `DimView`) dependencies for a given view. + /// INTERNAL API - Collects dimension (where Width or Height is `DimView`) dependencies for a given view. /// /// The dimension (width or height) to collect dependencies for. /// The view for which to collect dimension dependencies. @@ -891,7 +879,7 @@ internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View /// internal void CollectDim (Dim? dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) { - if (dim!.Has (out var dv)) + if (dim!.Has (out DimView dv)) { if (dv.Target != this) { @@ -899,21 +887,15 @@ internal void CollectDim (Dim? dim, View from, ref HashSet nNodes, ref Has } } - if (dim!.Has (out var dc)) + if (dim!.Has (out DimCombine dc)) { CollectDim (dc.Left, from, ref nNodes, ref nEdges); CollectDim (dc.Right, from, ref nNodes, ref nEdges); } - - if (dim!.Has (out var df)) - { - - } - } /// - /// Collects position (where X or Y is `PosView`) dependencies for a given view. + /// INTERNAL API - Collects position (where X or Y is `PosView`) dependencies for a given view. /// /// The position (X or Y) to collect dependencies for. /// The view for which to collect position dependencies. @@ -924,6 +906,7 @@ internal void CollectDim (Dim? dim, View from, ref HashSet nNodes, ref Has /// internal void CollectPos (Pos pos, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) { + // TODO: Use Pos.Has instead. switch (pos) { case PosView pv: diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index ef0ecbe213..12387317ee 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -74,7 +74,9 @@ namespace Terminal.Gui; /// To flag the entire view for redraw call . /// /// -/// The method is invoked when the size or layout of a view has changed. +/// The method is called when the size or layout of a view has changed. The will +/// cause to be called on the next so there is normally no reason to direclty call +/// see . /// /// /// Views have a property that defines the default colors that subviews should use for @@ -243,8 +245,8 @@ public virtual void EndInit () } } - // TOOD: Figure out how to move this out of here and just depend on IsLayoutNeeded in Mainloop - LayoutSubviews(); + // TODO: Figure out how to move this out of here and just depend on IsLayoutNeeded in Mainloop + Layout (); Initialized?.Invoke (this, EventArgs.Empty); } diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index a4209ffe42..5acb61f104 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -90,10 +90,7 @@ public void ApplyStyleChanges () CreateTextField (); SelectedColor = oldValue; - if (IsInitialized) - { - LayoutSubviews (); - } + SetLayoutNeeded (); } /// @@ -275,6 +272,8 @@ private void SyncSubViewValues (bool syncBars) { _tfHex.Text = colorHex; } + + SetLayoutNeeded (); } private void UpdateSingleBarValueFromTextField (object? sender, HasFocusEventArgs e) diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index 2ded19418f..2818afa070 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -189,7 +189,7 @@ internal FileDialog (IFileSystem fileSystem) bool newState = !tile.ContentView.Visible; tile.ContentView.Visible = newState; _btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState); - LayoutSubviews (); + SetLayoutNeeded(); }; _tbFind = new TextField @@ -493,7 +493,8 @@ public override void OnLoaded () _btnOk.X = Pos.Right (_btnCancel) + 1; MoveSubviewTowardsStart (_btnCancel); } - LayoutSubviews (); + + SetLayoutNeeded(); } /// diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 24dc68ef97..072447c9e7 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -5,6 +5,8 @@ // Miguel de Icaza (miguel@gnome.org) // +using System.Diagnostics; + namespace Terminal.Gui; /// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical @@ -259,7 +261,7 @@ public int Size { SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size); ShowHideScrollBars (false); - SetNeedsDisplay (); + SetLayoutNeeded (); } } } diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index d605b6fa05..8c6364451d 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -147,12 +147,27 @@ public Tab SelectedTab OnSelectedTabChanged (old, value); } + SetLayoutNeeded (); } } + private TabStyle _style = new (); + /// Render choices for how to display tabs. After making changes, call . /// - public TabStyle Style { get; set; } = new (); + public TabStyle Style + { + get => _style; + set + { + if (_style == value) + { + return; + } + _style = value; + SetLayoutNeeded(); + } + } /// All tabs currently hosted by the control. /// @@ -163,7 +178,11 @@ public Tab SelectedTab public int TabScrollOffset { get => _tabScrollOffset; - set => _tabScrollOffset = EnsureValidScrollOffsets (value); + set + { + _tabScrollOffset = EnsureValidScrollOffsets (value); + SetLayoutNeeded (); + } } /// Adds the given to . @@ -188,7 +207,7 @@ public void AddTab (Tab tab, bool andSelect) tab.View?.SetFocus (); } - SetNeedsDisplay (); + SetLayoutNeeded (); } /// @@ -245,12 +264,7 @@ public void ApplyStyleChanges () // Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0 } - if (IsInitialized) - { - LayoutSubviews (); - } - - SetNeedsDisplay (); + SetLayoutNeeded(); } /// Updates to ensure that is visible. @@ -327,7 +341,7 @@ public void RemoveTab (Tab tab) } EnsureSelectedTabIsVisible (); - SetNeedsDisplay (); + SetLayoutNeeded (); } /// Event for when changes. @@ -349,7 +363,6 @@ public bool SwitchTabBy (int amount) if (Tabs.Count == 1 || SelectedTab is null) { SelectedTab = Tabs.ElementAt (0); - SetNeedsDisplay (); return SelectedTab is { }; } @@ -359,9 +372,7 @@ public bool SwitchTabBy (int amount) // Currently selected tab has vanished! if (currentIdx == -1) { - SelectedTab = Tabs.ElementAt (0); - SetNeedsDisplay (); - + SelectedTab = Tabs.ElementAt (0); return true; } @@ -373,7 +384,6 @@ public bool SwitchTabBy (int amount) } SelectedTab = _tabs [newIdx]; - SetNeedsDisplay (); EnsureSelectedTabIsVisible (); @@ -611,7 +621,7 @@ protected override bool OnMouseEvent (MouseEventArgs me) { _host.SwitchTabBy (scrollIndicatorHit); - SetNeedsDisplay (); + SetLayoutNeeded (); return true; } @@ -619,7 +629,7 @@ protected override bool OnMouseEvent (MouseEventArgs me) if (hit is { }) { _host.SelectedTab = hit; - SetNeedsDisplay (); + SetLayoutNeeded (); return true; } @@ -1256,7 +1266,8 @@ private void RenderTabLine () tab.Text = toRender.TextToRender; - LayoutSubviews (); + // BUGBUG: Layout should only be called from Mainloop iteration! + Layout (); tab.OnDrawAdornments (); diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index fdf2c29512..72640e16f0 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -23,6 +23,23 @@ public TileView (int tiles) { CanFocus = true; RebuildForTileCount (tiles); + + LayoutStarted += (_, _) => + { + Rectangle viewport = Viewport; + + if (HasBorder ()) + { + viewport = new ( + viewport.X + 1, + viewport.Y + 1, + Math.Max (0, viewport.Width - 2), + Math.Max (0, viewport.Height - 2) + ); + } + + Setup (viewport); + }; } /// The line style to use when drawing the splitter lines. @@ -34,12 +51,14 @@ public Orientation Orientation get => _orientation; set { - _orientation = value; - - if (IsInitialized) + if (_orientation == value) { - LayoutSubviews (); + return; } + + _orientation = value; + + SetLayoutNeeded (); } } @@ -131,13 +150,7 @@ public Tile InsertTile (int idx) } } - SetNeedsDisplay (); - - if (IsInitialized) - { - LayoutSubviews (); - } - + SetLayoutNeeded (); return toReturn; } @@ -155,31 +168,6 @@ public Tile InsertTile (int idx) /// public bool IsRootTileView () { return _parentTileView == null; } - // TODO: Use OnLayoutStarted instead - /// - public override void LayoutSubviews () - { - if (!IsInitialized) - { - return; - } - - Rectangle viewport = Viewport; - - if (HasBorder ()) - { - viewport = new ( - viewport.X + 1, - viewport.Y + 1, - Math.Max (0, viewport.Width - 2), - Math.Max (0, viewport.Height - 2) - ); - } - - Setup (viewport); - base.LayoutSubviews (); - } - // BUG: v2 fix this hack // QUESTION: Does this need to be fixed before events are refactored? /// Overridden so no Frames get drawn @@ -364,12 +352,8 @@ public void RebuildForTileCount (int count) _tiles.Add (tile); tile.ContentView.Id = $"Tile.ContentView {i}"; Add (tile.ContentView); - tile.TitleChanged += (s, e) => SetNeedsDisplay (); - } - - if (IsInitialized) - { - LayoutSubviews (); + // BUGBUG: This should not be needed: + tile.TitleChanged += (s, e) => SetLayoutNeeded (); } } @@ -407,9 +391,6 @@ public Tile RemoveTile (int idx) Add (_tiles [i].ContentView); } - SetNeedsDisplay (); - LayoutSubviews (); - return removed; } @@ -441,7 +422,6 @@ public bool SetSplitterPos (int idx, Pos value) } _splitterDistances [idx] = value; - GetRootTileView ().LayoutSubviews (); OnSplitterMoved (idx); return true; @@ -957,7 +937,7 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) moveRuneRenderLocation = new Point (0, Math.Max (1, Math.Min (Viewport.Height - 2, mouseEvent.Position.Y))); } - Parent.SetNeedsDisplay (); + Parent.SetLayoutNeeded (); return true; } diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 291fc955e1..7861fcfd0b 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -235,7 +235,7 @@ out int ny return; } - var layoutSubviews = false; + //var layoutSubviews = false; var maxWidth = 0; if (superView.Margin is { } && superView == top.SuperView) @@ -251,36 +251,26 @@ out int ny if (top?.X is null or PosAbsolute && top?.Frame.X != nx) { top!.X = nx; - layoutSubviews = true; + //layoutSubviews = true; } if (top?.Y is null or PosAbsolute && top?.Frame.Y != ny) { top!.Y = ny; - layoutSubviews = true; + //layoutSubviews = true; } } - //// TODO: v2 - This is a hack to get the StatusBar to be positioned correctly. - //if (sb != null - // && !top!.Subviews.Contains (sb) - // && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) - // && top.Height is DimFill - // && -top.Height.GetAnchor (0) < 1) + + //if (superView.IsLayoutNeeded () || layoutSubviews) //{ - // top.Height = Dim.Fill (sb.Visible ? 1 : 0); - // layoutSubviews = true; + // superView.LayoutSubviews (); //} - if (superView.IsLayoutNeeded () || layoutSubviews) - { - superView.LayoutSubviews (); - } - - if (IsLayoutNeeded ()) - { - LayoutSubviews (); - } + //if (IsLayoutNeeded ()) + //{ + // LayoutSubviews (); + //} } /// Invoked when the terminal has been resized. The new of the terminal is provided. diff --git a/Terminal.Gui/Views/Wizard/Wizard.cs b/Terminal.Gui/Views/Wizard/Wizard.cs index bb19030348..2f88ce2e18 100644 --- a/Terminal.Gui/Views/Wizard/Wizard.cs +++ b/Terminal.Gui/Views/Wizard/Wizard.cs @@ -546,9 +546,6 @@ private void UpdateButtonsAndTitle () SizeStep (CurrentStep); SetLayoutNeeded (); - LayoutSubviews (); - - //Draw (); } private void Wizard_Closing (object sender, ToplevelClosingEventArgs obj) diff --git a/UICatalog/Scenarios/BorderEditor.cs b/UICatalog/Scenarios/BorderEditor.cs index ce5df6dcec..375ff2c5ab 100644 --- a/UICatalog/Scenarios/BorderEditor.cs +++ b/UICatalog/Scenarios/BorderEditor.cs @@ -29,7 +29,7 @@ private void BorderEditor_Initialized (object sender, EventArgs e) { List borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast ().ToList (); - _rbBorderStyle = new() + _rbBorderStyle = new () { X = 0, @@ -46,7 +46,7 @@ private void BorderEditor_Initialized (object sender, EventArgs e) _rbBorderStyle.SelectedItemChanged += OnRbBorderStyleOnSelectedItemChanged; - _ckbTitle = new() + _ckbTitle = new () { X = 0, Y = Pos.Bottom (_rbBorderStyle), @@ -91,7 +91,7 @@ void OnRbBorderStyleOnSelectedItemChanged (object s, SelectedItemChangedArgs e) } ((Border)AdornmentToEdit).SetNeedsDisplay (); - LayoutSubviews (); + SetLayoutNeeded (); } void OnCkbTitleOnToggle (object sender, CancelEventArgs args) diff --git a/UICatalog/Scenarios/ColorPicker.cs b/UICatalog/Scenarios/ColorPicker.cs index c059319129..8c2682ef3b 100644 --- a/UICatalog/Scenarios/ColorPicker.cs +++ b/UICatalog/Scenarios/ColorPicker.cs @@ -218,7 +218,6 @@ public override void Main () // Set default colors. foregroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Foreground.GetClosestNamedColor16 (); backgroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Background.GetClosestNamedColor16 (); - app.Initialized += (s, e) => app.LayoutSubviews (); Application.Run (app); app.Dispose (); diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs index bdf10337cf..12696543f1 100644 --- a/UICatalog/Scenarios/ComputedLayout.cs +++ b/UICatalog/Scenarios/ComputedLayout.cs @@ -370,7 +370,6 @@ public override void Main () // The call to app.LayoutSubviews causes the Computed layout to // get updated. anchorButton.Text += "!"; - app.LayoutSubviews (); }; app.Add (anchorButton); @@ -415,10 +414,7 @@ public override void Main () { // This demonstrates how to have a dynamically sized button // Each time the button is clicked the button's text gets longer - // The call to app.LayoutSubviews causes the Computed layout to - // get updated. leftButton.Text += "!"; - app.LayoutSubviews (); }; // show positioning vertically using Pos.AnchorEnd @@ -433,10 +429,7 @@ public override void Main () { // This demonstrates how to have a dynamically sized button // Each time the button is clicked the button's text gets longer - // The call to app.LayoutSubviews causes the Computed layout to - // get updated. centerButton.Text += "!"; - app.LayoutSubviews (); }; // show positioning vertically using another window and Pos.Bottom @@ -451,10 +444,7 @@ public override void Main () { // This demonstrates how to have a dynamically sized button // Each time the button is clicked the button's text gets longer - // The call to app.LayoutSubviews causes the Computed layout to - // get updated. rightButton.Text += "!"; - app.LayoutSubviews (); }; View [] buttons = { leftButton, centerButton, rightButton }; diff --git a/UICatalog/Scenarios/ConfigurationEditor.cs b/UICatalog/Scenarios/ConfigurationEditor.cs index 2c898ac8b8..5f40f51ddd 100644 --- a/UICatalog/Scenarios/ConfigurationEditor.cs +++ b/UICatalog/Scenarios/ConfigurationEditor.cs @@ -154,8 +154,6 @@ private void Open () { _tileView.Tiles.ToArray () [1].ContentView.SetFocus (); } - - Application.Top.LayoutSubviews (); } private void Quit () diff --git a/UICatalog/Scenarios/Dialogs.cs b/UICatalog/Scenarios/Dialogs.cs index 9a4156a394..8cddafcce2 100644 --- a/UICatalog/Scenarios/Dialogs.cs +++ b/UICatalog/Scenarios/Dialogs.cs @@ -340,7 +340,6 @@ Label buttonPressedLabel button.Text += char.ConvertFromUtf32 (CODE_POINT); } - dialog.LayoutSubviews (); e.Cancel = true; }; dialog.Add (addChar); diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 13e32bf5ec..dd44d29800 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -306,9 +306,6 @@ public override void Main () _scrollBar.OtherScrollBarView.Size = _textView.Maxlength; _scrollBar.OtherScrollBarView.Position = _textView.LeftColumn; } - - _scrollBar.LayoutSubviews (); - _scrollBar.Refresh (); }; diff --git a/UICatalog/Scenarios/LineDrawing.cs b/UICatalog/Scenarios/LineDrawing.cs index 38ea3bbfbb..e3a5eaf0f7 100644 --- a/UICatalog/Scenarios/LineDrawing.cs +++ b/UICatalog/Scenarios/LineDrawing.cs @@ -253,12 +253,8 @@ public override void BeginInit () private void ToolsView_Initialized (object sender, EventArgs e) { - LayoutSubviews (); - Width = Math.Max (_colors.Frame.Width, _stylePicker.Frame.Width) + GetAdornmentsThickness ().Horizontal; - Height = _colors.Frame.Height + _stylePicker.Frame.Height + _addLayerBtn.Frame.Height + GetAdornmentsThickness ().Vertical; - SuperView.LayoutSubviews (); } } @@ -289,6 +285,9 @@ public override void OnDrawContentComplete (Rectangle viewport) } } } + + // TODO: This is a hack to work around overlapped views not drawing correctly. + SuperView?.SetLayoutNeeded(); } //// BUGBUG: Why is this not handled by a key binding??? diff --git a/UICatalog/Scenarios/PosAlignDemo.cs b/UICatalog/Scenarios/PosAlignDemo.cs index b5aa98b312..765a8e452e 100644 --- a/UICatalog/Scenarios/PosAlignDemo.cs +++ b/UICatalog/Scenarios/PosAlignDemo.cs @@ -335,8 +335,6 @@ private void UpdatePosAlignObjects (View superView, Dimension dimension, Aligner } } } - - superView.LayoutSubviews (); } /// diff --git a/UICatalog/Scenarios/Progress.cs b/UICatalog/Scenarios/Progress.cs index 174ca266cb..c87fc47a39 100644 --- a/UICatalog/Scenarios/Progress.cs +++ b/UICatalog/Scenarios/Progress.cs @@ -304,7 +304,6 @@ internal void Start () { Spinner.Visible = true; ActivityProgressBar.Width = Dim.Fill () - Spinner.Width; - LayoutSubviews (); } ); } @@ -319,7 +318,6 @@ internal void Stop () { Spinner.Visible = false; ActivityProgressBar.Width = Dim.Fill () - Spinner.Width; - LayoutSubviews (); } ); } diff --git a/UICatalog/Scenarios/Scrolling.cs b/UICatalog/Scenarios/Scrolling.cs index fcd4cddaee..8d7cdac009 100644 --- a/UICatalog/Scenarios/Scrolling.cs +++ b/UICatalog/Scenarios/Scrolling.cs @@ -134,10 +134,8 @@ public override void Main () { // This demonstrates how to have a dynamically sized button // Each time the button is clicked the button's text gets longer - // The call to Win.LayoutSubviews causes the Computed layout to - // get updated. anchorButton.Text += "!"; - app.LayoutSubviews (); + }; scrollView.Add (anchorButton); diff --git a/UICatalog/Scenarios/Sliders.cs b/UICatalog/Scenarios/Sliders.cs index 393b9768fc..e0bc361740 100644 --- a/UICatalog/Scenarios/Sliders.cs +++ b/UICatalog/Scenarios/Sliders.cs @@ -219,11 +219,6 @@ public override void Main () } } } - - if (app.IsInitialized) - { - app.LayoutSubviews (); - } }; optionsSlider.SetOption (0); // Legends optionsSlider.SetOption (1); // RangeAllowSingle @@ -326,8 +321,6 @@ public override void Main () } } } - - app.LayoutSubviews (); }; #endregion Slider Orientation Slider @@ -384,7 +377,6 @@ public override void Main () } } - app.LayoutSubviews (); }; #endregion Legends Orientation Slider diff --git a/UICatalog/Scenarios/TileViewNesting.cs b/UICatalog/Scenarios/TileViewNesting.cs index 728fdf26f8..9882865ec8 100644 --- a/UICatalog/Scenarios/TileViewNesting.cs +++ b/UICatalog/Scenarios/TileViewNesting.cs @@ -35,16 +35,16 @@ public override void Main () _textField.TextChanged += (s, e) => SetupTileView (); _cbHorizontal = new() { X = Pos.Right (_textField) + 1, Text = "Horizontal" }; - _cbHorizontal.CheckedStateChanging += (s, e) => SetupTileView (); + _cbHorizontal.CheckedStateChanged += (s, e) => SetupTileView (); _cbBorder = new() { X = Pos.Right (_cbHorizontal) + 1, Text = "Border" }; - _cbBorder.CheckedStateChanging += (s, e) => SetupTileView (); + _cbBorder.CheckedStateChanged += (s, e) => SetupTileView (); _cbTitles = new() { X = Pos.Right (_cbBorder) + 1, Text = "Titles" }; - _cbTitles.CheckedStateChanging += (s, e) => SetupTileView (); + _cbTitles.CheckedStateChanged += (s, e) => SetupTileView (); _cbUseLabels = new() { X = Pos.Right (_cbTitles) + 1, Text = "Use Labels" }; - _cbUseLabels.CheckedStateChanging += (s, e) => SetupTileView (); + _cbUseLabels.CheckedStateChanged += (s, e) => SetupTileView (); _workArea = new() { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill () }; @@ -196,11 +196,6 @@ private void SetupTileView () _viewsToCreate = numberOfViews; AddMoreViews (root); } - - if (_loaded) - { - _workArea.LayoutSubviews (); - } } private void Split (TileView to, bool left) diff --git a/UICatalog/Scenarios/Wizards.cs b/UICatalog/Scenarios/Wizards.cs index e26c717e8c..245a95503a 100644 --- a/UICatalog/Scenarios/Wizards.cs +++ b/UICatalog/Scenarios/Wizards.cs @@ -336,9 +336,6 @@ void Win_Loaded (object sender, EventArgs args) scrollBar.OtherScrollBarView.Size = someText.Maxlength; scrollBar.OtherScrollBarView.Position = someText.LeftColumn; } - - scrollBar.LayoutSubviews (); - scrollBar.Refresh (); }; fourthStep.Add (scrollBar); diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index d373c331e8..90970b3bef 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -546,7 +546,7 @@ public void Invoke_Adds_Idle () var actionCalled = 0; Application.Invoke (() => { actionCalled++; }); Application.MainLoop.Running = true; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); Assert.Equal (1, actionCalled); top.Dispose (); Application.Shutdown (); diff --git a/UnitTests/Dialogs/DialogTests.cs b/UnitTests/Dialogs/DialogTests.cs index b5efdb5bed..82c3e9f3a2 100644 --- a/UnitTests/Dialogs/DialogTests.cs +++ b/UnitTests/Dialogs/DialogTests.cs @@ -51,8 +51,8 @@ public void Add_Button_Works () // Now add a second button buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {CM.Glyphs.VLine}"; dlg.AddButton (new () { Text = btn2Text }); - var first = false; - RunIteration (ref runstate, ref first); + + RunIteration (ref runstate); TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); dlg.Dispose (); @@ -76,8 +76,7 @@ public void Add_Button_Works () // Now add a second button buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2}{CM.Glyphs.VLine}"; dlg.AddButton (new () { Text = btn2Text }); - first = false; - RunIteration (ref runstate, ref first); + RunIteration (ref runstate); TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); dlg.Dispose (); @@ -101,8 +100,8 @@ public void Add_Button_Works () // Now add a second button buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2}{CM.Glyphs.VLine}"; dlg.AddButton (new () { Text = btn2Text }); - first = false; - RunIteration (ref runstate, ref first); + + RunIteration (ref runstate); TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); dlg.Dispose (); @@ -126,8 +125,8 @@ public void Add_Button_Works () // Now add a second button buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {CM.Glyphs.VLine}"; dlg.AddButton (new () { Text = btn2Text }); - first = false; - RunIteration (ref runstate, ref first); + + RunIteration (ref runstate); TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); dlg.Dispose (); @@ -849,7 +848,7 @@ public void ButtonAlignment_Two_Hidden () button2 = new Button { Text = btn2Text }; (runstate, dlg) = RunButtonTestDialog (title, width, Alignment.Center, button1, button2); button1.Visible = false; - RunIteration (ref runstate, ref firstIteration); + RunIteration (ref runstate, firstIteration); buttonRow = $@"{CM.Glyphs.VLine} {btn2} {CM.Glyphs.VLine}"; TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); @@ -861,7 +860,7 @@ public void ButtonAlignment_Two_Hidden () button2 = new Button { Text = btn2Text }; (runstate, dlg) = RunButtonTestDialog (title, width, Alignment.Fill, button1, button2); button1.Visible = false; - RunIteration (ref runstate, ref firstIteration); + RunIteration (ref runstate, firstIteration); buttonRow = $@"{CM.Glyphs.VLine} {btn2}{CM.Glyphs.VLine}"; TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); @@ -873,7 +872,7 @@ public void ButtonAlignment_Two_Hidden () button2 = new Button { Text = btn2Text }; (runstate, dlg) = RunButtonTestDialog (title, width, Alignment.End, button1, button2); button1.Visible = false; - RunIteration (ref runstate, ref firstIteration); + RunIteration (ref runstate, firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); dlg.Dispose (); @@ -884,7 +883,7 @@ public void ButtonAlignment_Two_Hidden () button2 = new Button { Text = btn2Text }; (runstate, dlg) = RunButtonTestDialog (title, width, Alignment.Start, button1, button2); button1.Visible = false; - RunIteration (ref runstate, ref firstIteration); + RunIteration (ref runstate, firstIteration); buttonRow = $@"{CM.Glyphs.VLine} {btn2} {CM.Glyphs.VLine}"; TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); diff --git a/UnitTests/Dialogs/WizardTests.cs b/UnitTests/Dialogs/WizardTests.cs index c8ef785cbf..accd381398 100644 --- a/UnitTests/Dialogs/WizardTests.cs +++ b/UnitTests/Dialogs/WizardTests.cs @@ -31,10 +31,10 @@ public void Finish_Button_Closes () RunState runstate = Application.Begin (wizard); var firstIteration = true; - Application.RunIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, firstIteration); wizard.NextFinishButton.InvokeCommand (Command.Accept); - Application.RunIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, firstIteration); Application.End (runstate); Assert.True (finishedFired); Assert.True (closedFired); @@ -56,7 +56,7 @@ public void Finish_Button_Closes () wizard.Closed += (s, e) => { closedFired = true; }; runstate = Application.Begin (wizard); - Application.RunIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, firstIteration); Assert.Equal (step1.Title, wizard.CurrentStep.Title); wizard.NextFinishButton.InvokeCommand (Command.Accept); @@ -90,7 +90,7 @@ public void Finish_Button_Closes () wizard.Closed += (s, e) => { closedFired = true; }; runstate = Application.Begin (wizard); - Application.RunIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, firstIteration); Assert.Equal (step2.Title, wizard.CurrentStep.Title); Assert.Equal (wizard.GetLastStep ().Title, wizard.CurrentStep.Title); @@ -464,7 +464,7 @@ public void OneStepWizard_Shows () //wizard.LayoutSubviews (); var firstIteration = false; RunState runstate = Application.Begin (wizard); - Application.RunIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, firstIteration); // TODO: Disabled until Dim.Auto is used in Dialog //TestHelpers.AssertDriverContentsWithFrameAre ( diff --git a/UnitTests/View/Adornment/BorderTests.cs b/UnitTests/View/Adornment/BorderTests.cs index c77d3d4997..29882235f5 100644 --- a/UnitTests/View/Adornment/BorderTests.cs +++ b/UnitTests/View/Adornment/BorderTests.cs @@ -96,7 +96,7 @@ public void Border_With_Title_Border_Double_Thickness_Top_Four_Size_Width (int w var firstIteration = false; ((FakeDriver)Application.Driver!).SetBufferSize (width, 5); - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; switch (width) @@ -227,10 +227,9 @@ public void Border_With_Title_Border_Double_Thickness_Top_Three_Size_Width (int win.Border.Thickness = win.Border.Thickness with { Top = 3 }; RunState rs = Application.Begin (win); - var firstIteration = false; ((FakeDriver)Application.Driver!).SetBufferSize (width, 4); - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, false); var expected = string.Empty; switch (width) @@ -364,7 +363,7 @@ public void Border_With_Title_Border_Double_Thickness_Top_Two_Size_Width (int wi var firstIteration = false; ((FakeDriver)Application.Driver!).SetBufferSize (width, 4); - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; switch (width) @@ -487,7 +486,7 @@ public void Border_With_Title_Size_Height (int height) var firstIteration = false; ((FakeDriver)Application.Driver!).SetBufferSize (20, height); - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; switch (height) @@ -549,7 +548,7 @@ public void Border_With_Title_Size_Width (int width) var firstIteration = false; ((FakeDriver)Application.Driver!).SetBufferSize (width, 3); - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; switch (width) @@ -729,7 +728,7 @@ public void HasSuperView () var firstIteration = false; ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); var expected = @" ╔═══╗ @@ -757,7 +756,7 @@ public void HasSuperView_Title () var firstIteration = false; ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); var expected = @" ╔════════╗ @@ -780,7 +779,7 @@ public void NoSuperView () var firstIteration = false; ((FakeDriver)Application.Driver!).SetBufferSize (3, 3); - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); var expected = @" ┌─┐ diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs index 2e02ecc1ad..faf7288845 100644 --- a/UnitTests/View/DrawTests.cs +++ b/UnitTests/View/DrawTests.cs @@ -7,6 +7,139 @@ namespace Terminal.Gui.ViewTests; [Trait ("Category", "Output")] public class DrawTests (ITestOutputHelper _output) { + [Fact] + public void NeedsDisplay_True_After_Constructor () + { + var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + Assert.True (view.NeedsDisplay); + } + + [Fact] + public void NeedsDisplay_False_After_BeginInit () + { + var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + Assert.True (view.NeedsDisplay); + + view.BeginInit (); + Assert.True (view.NeedsDisplay); + + view.NeedsDisplay = false; + + view.BeginInit (); + Assert.False (view.NeedsDisplay); + } + + [Fact] + public void NeedsDisplay_False_After_EndInit () + { + var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + Assert.True (view.NeedsDisplay); + + view.BeginInit (); + Assert.True (view.NeedsDisplay); + + view.EndInit (); + Assert.True (view.NeedsDisplay); + + view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + view.BeginInit (); + view.NeedsDisplay = false; + view.EndInit (); + Assert.False (view.NeedsDisplay); + } + + [Fact] + public void NeedsDisplay_False_After_SetRelativeLayout () + { + var view = new View { Width = 2, Height = 2 }; + Assert.True (view.NeedsDisplay); + + view.BeginInit (); + Assert.True (view.NeedsDisplay); + + view.EndInit (); + Assert.True (view.NeedsDisplay); + + view.SetRelativeLayout (Application.Screen.Size); + Assert.True (view.NeedsDisplay); + + view.NeedsDisplay = false; + view.SetRelativeLayout (new (10, 10)); + Assert.False (view.NeedsDisplay); + + view = new View { Width = Dim.Percent(50), Height = Dim.Percent(50) }; + View superView = new () + { + Id = "superView", + Width = Dim.Fill(), + Height = Dim.Fill() + }; + superView.Add (view); + + superView.BeginInit (); + Assert.True (view.NeedsDisplay); + Assert.True (superView.NeedsDisplay); + + superView.EndInit (); + Assert.True (view.NeedsDisplay); + Assert.True (superView.NeedsDisplay); + + superView.SetRelativeLayout (Application.Screen.Size); + Assert.True (view.NeedsDisplay); + Assert.True (superView.NeedsDisplay); + + superView.NeedsDisplay = false; + superView.SetRelativeLayout (new (10, 10)); + Assert.False (superView.NeedsDisplay); + Assert.False (view.NeedsDisplay); + + view.SetRelativeLayout (new (11, 11)); + Assert.True (superView.NeedsDisplay); + Assert.True (view.NeedsDisplay); + + } + + [Fact] + public void NeedsDisplay_True_After_LayoutSubviews () + { + var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + Assert.True (view.NeedsDisplay); + + view.BeginInit (); + Assert.True (view.NeedsDisplay); + + view.EndInit (); + Assert.True (view.NeedsDisplay); + + view.SetRelativeLayout (Application.Screen.Size); + Assert.True (view.NeedsDisplay); + + view.LayoutSubviews (); + Assert.True (view.NeedsDisplay); + } + + [Fact] + public void NeedsDisplay_False_After_Draw () + { + var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + Assert.True (view.NeedsDisplay); + + view.BeginInit (); + Assert.True (view.NeedsDisplay); + + view.EndInit (); + Assert.True (view.NeedsDisplay); + + view.SetRelativeLayout (Application.Screen.Size); + Assert.True (view.NeedsDisplay); + + view.LayoutSubviews (); + Assert.True (view.NeedsDisplay); + + view.Draw (); + Assert.False (view.NeedsDisplay); + } + [Fact] [SetupFakeDriver] public void Move_Is_Constrained_To_Viewport () @@ -17,18 +150,18 @@ public void Move_Is_Constrained_To_Viewport () Y = 1, Width = 3, Height = 3 }; - view.Margin.Thickness = new Thickness (1); + view.Margin.Thickness = new (1); // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen) view.Move (0, 0); - Assert.Equal (new Point (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row)); + Assert.Equal (new (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row)); view.Move (-1, -1); - Assert.Equal (new Point (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row)); + Assert.Equal (new (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row)); view.Move (1, 1); - Assert.Equal (new Point (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row)); + Assert.Equal (new (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row)); } [Fact] @@ -41,7 +174,7 @@ public void AddRune_Is_Constrained_To_Viewport () Y = 1, Width = 3, Height = 3 }; - view.Margin.Thickness = new Thickness (1); + view.Margin.Thickness = new (1); View.Diagnostics = ViewDiagnosticFlags.Padding; view.BeginInit (); view.EndInit (); @@ -84,6 +217,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) superView.LayoutSubviews (); superView.Draw (); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -93,6 +227,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) Rectangle toFill = new (x, y, width, height); view.FillRect (toFill); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -103,6 +238,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) // Now try to clear beyond Viewport (invalid; clipping should prevent) superView.SetNeedsDisplay (); superView.Draw (); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -111,6 +247,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) _output); toFill = new (-width, -height, width, height); view.FillRect (toFill); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -121,6 +258,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) // Now try to clear beyond Viewport (valid) superView.SetNeedsDisplay (); superView.Draw (); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -129,6 +267,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) _output); toFill = new (-1, -1, width + 1, height + 1); view.FillRect (toFill); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -139,6 +278,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) // Now clear too much size superView.SetNeedsDisplay (); superView.Draw (); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -147,6 +287,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) _output); toFill = new (0, 0, width * 2, height * 2); view.FillRect (toFill); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -174,6 +315,7 @@ public void Clear_ClearsEntireViewport () superView.LayoutSubviews (); superView.Draw (); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -182,6 +324,7 @@ public void Clear_ClearsEntireViewport () _output); view.Clear (); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -210,6 +353,7 @@ public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly () superView.LayoutSubviews (); superView.Draw (); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -218,6 +362,7 @@ public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly () _output); view.Clear (); + TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌─┐ @@ -226,7 +371,6 @@ public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly () _output); } - [Fact] [AutoInitShutdown] [Trait ("Category", "Unicode")] @@ -243,7 +387,7 @@ public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two () Assert.Equal (2, r.GetColumns ()); var win = new Window { Title = us }; - var view = new View { Text = r.ToString (), Height = Dim.Fill (), Width = Dim.Fill ()}; + var view = new View { Text = r.ToString (), Height = Dim.Fill (), Width = Dim.Fill () }; var tf = new TextField { Text = us, Y = 1, Width = 3 }; win.Add (view, tf); Toplevel top = new (); @@ -317,7 +461,7 @@ public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_Wi """; Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output); - Assert.Equal (new Rectangle (0, 0, 30, 10), pos); + Assert.Equal (new (0, 0, 30, 10), pos); Application.End (rsDiag); dg.Dispose (); @@ -352,12 +496,13 @@ public void Colors_On_TextAlignment_Right_And_Bottom () Toplevel top = new (); top.Add (viewRight, viewBottom); - Application.Begin (top); + var rs = Application.Begin (top); ((FakeDriver)Application.Driver!).SetBufferSize (7, 7); + Application.RunIteration (ref rs); TestHelpers.AssertDriverContentsWithFrameAre ( """ - + Test @@ -391,13 +536,25 @@ Colors.ColorSchemes ["Base"]!.Normal public void Draw_Minimum_Full_Border_With_Empty_Viewport () { var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + Assert.True (view.NeedsDisplay); + view.BeginInit (); + Assert.True (view.NeedsDisplay); + view.EndInit (); + Assert.True (view.NeedsDisplay); + view.SetRelativeLayout (Application.Screen.Size); + Assert.True (view.NeedsDisplay); + + view.LayoutSubviews (); + Assert.True (view.NeedsDisplay); Assert.Equal (new (0, 0, 2, 2), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); + Assert.True (view.NeedsDisplay); + view.Draw (); TestHelpers.AssertDriverContentsWithFrameAre ( @@ -415,7 +572,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport () public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Bottom () { var view = new View { Width = 2, Height = 1, BorderStyle = LineStyle.Single }; - view.Border.Thickness = new Thickness (1, 1, 1, 0); + view.Border.Thickness = new (1, 1, 1, 0); view.BeginInit (); view.EndInit (); view.SetRelativeLayout (Application.Screen.Size); @@ -433,7 +590,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Bottom () public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Left () { var view = new View { Width = 1, Height = 2, BorderStyle = LineStyle.Single }; - view.Border.Thickness = new Thickness (0, 1, 1, 1); + view.Border.Thickness = new (0, 1, 1, 1); view.BeginInit (); view.EndInit (); view.SetRelativeLayout (Application.Screen.Size); @@ -458,7 +615,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Left () public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Right () { var view = new View { Width = 1, Height = 2, BorderStyle = LineStyle.Single }; - view.Border.Thickness = new Thickness (1, 1, 0, 1); + view.Border.Thickness = new (1, 1, 0, 1); view.BeginInit (); view.EndInit (); view.SetRelativeLayout (Application.Screen.Size); @@ -483,7 +640,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Right () public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Top () { var view = new View { Width = 2, Height = 1, BorderStyle = LineStyle.Single }; - view.Border.Thickness = new Thickness (1, 0, 1, 1); + view.Border.Thickness = new (1, 0, 1, 1); view.BeginInit (); view.EndInit (); @@ -494,7 +651,8 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Top () view.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre ("││", + TestHelpers.AssertDriverContentsWithFrameAre ( + "││", _output ); } @@ -510,40 +668,40 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines () Width = 1, Height = 7, Text = """ - s - u - b - V - i - e - w - """ + s + u + b + V + i + e + w + """ }; var view = new View { Id = "view", Width = 2, Height = 20, Text = """ - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - """ + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + """ }; view.Add (subView); var content = new View { Id = "content", Width = 20, Height = 20 }; @@ -560,12 +718,12 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines () container.Add (content); Toplevel top = new (); top.Add (container); - Application.Driver!.Clip = container.Frame; - Application.Begin (top); + var rs = Application.Begin (top); + top.Draw (); TestHelpers.AssertDriverContentsWithFrameAre ( """ - + 0s 1u 2b @@ -580,7 +738,7 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines () TestHelpers.AssertDriverContentsWithFrameAre ( """ - + s u b @@ -600,7 +758,7 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines () TestHelpers.AssertDriverContentsWithFrameAre ( """ - + 1u 2b 3V @@ -615,7 +773,7 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines () TestHelpers.AssertDriverContentsWithFrameAre ( """ - + 6w 7 8 @@ -630,7 +788,7 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines () TestHelpers.AssertDriverContentsWithFrameAre ( """ - + 9 """, _output @@ -682,9 +840,10 @@ public void Draw_Negative_Viewport_Horizontal_Without_New_Lines () top.LayoutComplete += Top_LayoutComplete; Application.Begin (top); + Application.Refresh (); TestHelpers.AssertDriverContentsWithFrameAre ( """ - + 01234 subVi """, @@ -696,7 +855,7 @@ public void Draw_Negative_Viewport_Horizontal_Without_New_Lines () TestHelpers.AssertDriverContentsWithFrameAre ( """ - + 12345 ubVie """, @@ -708,7 +867,7 @@ public void Draw_Negative_Viewport_Horizontal_Without_New_Lines () TestHelpers.AssertDriverContentsWithFrameAre ( """ - + ubVie """, _output @@ -766,12 +925,12 @@ public void Draw_Negative_Viewport_Vertical () container.Add (content); Toplevel top = new (); top.Add (container); - Application.Driver!.Clip = container.Frame; Application.Begin (top); + Application.Refresh (); TestHelpers.AssertDriverContentsWithFrameAre ( """ - + 0s 1u 2b @@ -786,7 +945,7 @@ public void Draw_Negative_Viewport_Vertical () TestHelpers.AssertDriverContentsWithFrameAre ( """ - + s u b @@ -806,7 +965,7 @@ public void Draw_Negative_Viewport_Vertical () TestHelpers.AssertDriverContentsWithFrameAre ( """ - + 1u 2b 3V @@ -821,7 +980,7 @@ public void Draw_Negative_Viewport_Vertical () TestHelpers.AssertDriverContentsWithFrameAre ( """ - + 6w 7 8 @@ -836,7 +995,7 @@ public void Draw_Negative_Viewport_Vertical () TestHelpers.AssertDriverContentsWithFrameAre ( """ - + 9 """, _output @@ -892,15 +1051,16 @@ public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two () var expected = """ - ┌┤𝔹├─────┐ - │𝔹 │ - │𝔹 │ - └────────┘ - """; + ┌┤𝔹├─────┐ + │𝔹 │ + │𝔹 │ + └────────┘ + """; TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); TestHelpers.AssertDriverContentsAre (expected, _output); top.Dispose (); + // This test has nothing to do with color - removing as it is not relevant and fragile } @@ -916,15 +1076,16 @@ public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped () // Visible content is (1, 1, 10, 10) // Expected clip is (1, 1, 10, 10) - same as visible content Rectangle expectedClip = new (1, 1, 10, 10); + // Arrange - var view = new View () + var view = new View { Width = Dim.Fill (), Height = Dim.Fill (), ViewportSettings = ViewportSettings.ClipContentOnly }; view.SetContentSize (new Size (10, 10)); - view.Border.Thickness = new Thickness (1); + view.Border.Thickness = new (1); view.BeginInit (); view.EndInit (); Assert.Equal (view.Frame, Application.Driver?.Clip); @@ -949,14 +1110,15 @@ public void SetClip_Default_ClipsToViewport () // Visible content is (1, 1, 10, 10) // Expected clip is (1, 1, 23, 23) - same as Viewport Rectangle expectedClip = new (1, 1, 23, 23); + // Arrange - var view = new View () + var view = new View { Width = Dim.Fill (), - Height = Dim.Fill (), + Height = Dim.Fill () }; view.SetContentSize (new Size (10, 10)); - view.Border.Thickness = new Thickness (1); + view.Border.Thickness = new (1); view.BeginInit (); view.EndInit (); Assert.Equal (view.Frame, Application.Driver?.Clip); @@ -970,7 +1132,6 @@ public void SetClip_Default_ClipsToViewport () view.Dispose (); } - [Fact] [TestRespondersDisposed] public void Draw_Throws_IndexOutOfRangeException_With_Negative_Bounds () @@ -983,11 +1144,11 @@ public void Draw_Throws_IndexOutOfRangeException_With_Negative_Bounds () top.Add (view); Application.Iteration += (s, a) => - { - Assert.Equal (-2, view.X); + { + Assert.Equal (-2, view.X); - Application.RequestStop (); - }; + Application.RequestStop (); + }; try { @@ -1000,8 +1161,8 @@ public void Draw_Throws_IndexOutOfRangeException_With_Negative_Bounds () } top.Dispose (); + // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } - } diff --git a/UnitTests/View/Layout/AbsoluteLayoutTests.cs b/UnitTests/View/Layout/AbsoluteLayoutTests.cs deleted file mode 100644 index 1ed9fab03d..0000000000 --- a/UnitTests/View/Layout/AbsoluteLayoutTests.cs +++ /dev/null @@ -1,236 +0,0 @@ -using Xunit.Abstractions; - -namespace Terminal.Gui.LayoutTests; - -public class AbsoluteLayoutTests (ITestOutputHelper output) -{ - private readonly ITestOutputHelper _output = output; - - [Fact] - [TestRespondersDisposed] - public void AbsoluteLayout_Change_Height_or_Width_Absolute () - { - var frame = new Rectangle (1, 2, 3, 4); - var newFrame = new Rectangle (1, 2, 30, 40); - - var v = new View { Frame = frame }; - v.Height = newFrame.Height; - v.Width = newFrame.Width; - Assert.Equal (newFrame, v.Frame); - - Assert.Equal ( - new (0, 0, newFrame.Width, newFrame.Height), - v.Viewport - ); // With Absolute Viewport *is* deterministic before Layout - Assert.Equal (Pos.Absolute (1), v.X); - Assert.Equal (Pos.Absolute (2), v.Y); - Assert.Equal ($"Absolute({newFrame.Height})", v.Height.ToString ()); - Assert.Equal ($"Absolute({newFrame.Width})", v.Width.ToString ()); - v.Dispose (); - } - - [Fact] - [TestRespondersDisposed] - public void AbsoluteLayout_Change_Height_or_Width_MakesComputed () - { - var v = new View { Frame = Rectangle.Empty }; - v.Height = Dim.Fill (); - v.Width = Dim.Fill (); - v.Dispose (); - } - - [Fact] - [TestRespondersDisposed] - public void AbsoluteLayout_Change_X_or_Y_Absolute () - { - var frame = new Rectangle (1, 2, 3, 4); - var newFrame = new Rectangle (10, 20, 3, 4); - - var v = new View { Frame = frame }; - v.X = newFrame.X; - v.Y = newFrame.Y; - Assert.Equal (newFrame, v.Frame); - - Assert.Equal ( - new (0, 0, newFrame.Width, newFrame.Height), - v.Viewport - ); // With Absolute Viewport *is* deterministic before Layout - Assert.Equal ($"Absolute({newFrame.X})", v.X.ToString ()); - Assert.Equal ($"Absolute({newFrame.Y})", v.Y.ToString ()); - Assert.Equal (Dim.Absolute (3), v.Width); - Assert.Equal (Dim.Absolute (4), v.Height); - v.Dispose (); - } - - [Fact] - [TestRespondersDisposed] - public void AbsoluteLayout_Change_X_or_Y_MakesComputed () - { - var v = new View { Frame = Rectangle.Empty }; - v.X = Pos.Center (); - v.Y = Pos.Center (); - v.Dispose (); - } - - [Fact] - [TestRespondersDisposed] - public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute () - { - var v = new View { Frame = Rectangle.Empty }; - v.X = 1; - v.Y = 2; - v.Height = 3; - v.Width = 4; - v.Dispose (); - - v = new() { Frame = Rectangle.Empty }; - v.X = Pos.Center (); - v.Y = Pos.Center (); - v.Width = Dim.Fill (); - v.Height = Dim.Fill (); - v.Dispose (); - - v = new() { Frame = Rectangle.Empty }; - v.X = Pos.Center (); - v.Y = Pos.Center (); - v.Width = Dim.Fill (); - v.Height = Dim.Fill (); - - v.X = 1; - v.Dispose (); - - v = new() { Frame = Rectangle.Empty }; - v.X = Pos.Center (); - v.Y = Pos.Center (); - v.Width = Dim.Fill (); - v.Height = Dim.Fill (); - - v.Y = 2; - v.Dispose (); - - v = new() { Frame = Rectangle.Empty }; - v.X = Pos.Center (); - v.Y = Pos.Center (); - v.Width = Dim.Fill (); - v.Height = Dim.Fill (); - - v.Width = 3; - v.Dispose (); - - v = new() { Frame = Rectangle.Empty }; - v.X = Pos.Center (); - v.Y = Pos.Center (); - v.Width = Dim.Fill (); - v.Height = Dim.Fill (); - - v.Height = 3; - v.Dispose (); - - v = new() { Frame = Rectangle.Empty }; - v.X = Pos.Center (); - v.Y = Pos.Center (); - v.Width = Dim.Fill (); - v.Height = Dim.Fill (); - - v.X = 1; - v.Y = 2; - v.Height = 3; - v.Width = 4; - v.Dispose (); - } - - [Fact] - [TestRespondersDisposed] - public void AbsoluteLayout_Constructor () - { - var v = new View (); - v.Dispose (); - - var frame = Rectangle.Empty; - v = new() { Frame = frame }; - Assert.Equal (frame, v.Frame); - - Assert.Equal ( - new (0, 0, frame.Width, frame.Height), - v.Viewport - ); // With Absolute Viewport *is* deterministic before Layout - Assert.Equal (Pos.Absolute (0), v.X); - Assert.Equal (Pos.Absolute (0), v.Y); - Assert.Equal (Dim.Absolute (0), v.Width); - Assert.Equal (Dim.Absolute (0), v.Height); - v.Dispose (); - - frame = new (1, 2, 3, 4); - v = new() { Frame = frame }; - Assert.Equal (frame, v.Frame); - - Assert.Equal ( - new (0, 0, frame.Width, frame.Height), - v.Viewport - ); // With Absolute Viewport *is* deterministic before Layout - Assert.Equal (Pos.Absolute (1), v.X); - Assert.Equal (Pos.Absolute (2), v.Y); - Assert.Equal (Dim.Absolute (3), v.Width); - Assert.Equal (Dim.Absolute (4), v.Height); - v.Dispose (); - - v = new() { Frame = frame, Text = "v" }; - Assert.Equal (frame, v.Frame); - - Assert.Equal ( - new (0, 0, frame.Width, frame.Height), - v.Viewport - ); // With Absolute Viewport *is* deterministic before Layout - Assert.Equal (Pos.Absolute (1), v.X); - Assert.Equal (Pos.Absolute (2), v.Y); - Assert.Equal (Dim.Absolute (3), v.Width); - Assert.Equal (Dim.Absolute (4), v.Height); - v.Dispose (); - - v = new() { X = frame.X, Y = frame.Y, Text = "v" }; - - Assert.Equal (new (frame.X, frame.Y, 0, 0), v.Frame); - Assert.Equal (new (0, 0, 0, 0), v.Viewport); // With Absolute Viewport *is* deterministic before Layout - Assert.Equal (Pos.Absolute (1), v.X); - Assert.Equal (Pos.Absolute (2), v.Y); - Assert.Equal (Dim.Absolute (0), v.Width); - Assert.Equal (Dim.Absolute (0), v.Height); - v.Dispose (); - - v = new (); - Assert.Equal (new (0, 0, 0, 0), v.Frame); - Assert.Equal (new (0, 0, 0, 0), v.Viewport); // With Absolute Viewport *is* deterministic before Layout - Assert.Equal (Pos.Absolute (0), v.X); - Assert.Equal (Pos.Absolute (0), v.Y); - Assert.Equal (Dim.Absolute (0), v.Width); - Assert.Equal (Dim.Absolute (0), v.Height); - v.Dispose (); - - v = new() { X = frame.X, Y = frame.Y, Width = frame.Width, Height = frame.Height }; - Assert.Equal (new (frame.X, frame.Y, 3, 4), v.Frame); - Assert.Equal (new (0, 0, 3, 4), v.Viewport); // With Absolute Viewport *is* deterministic before Layout - Assert.Equal (Pos.Absolute (1), v.X); - Assert.Equal (Pos.Absolute (2), v.Y); - Assert.Equal (Dim.Absolute (3), v.Width); - Assert.Equal (Dim.Absolute (4), v.Height); - v.Dispose (); - } - - [Fact] - [TestRespondersDisposed] - public void AbsoluteLayout_LayoutSubviews () - { - var superRect = new Rectangle (0, 0, 100, 100); - var super = new View { Frame = superRect, Text = "super" }; - var v1 = new View { X = 0, Y = 0, Width = 10, Height = 10 }; - - var v2 = new View { X = 10, Y = 10, Width = 10, Height = 10 }; - - super.Add (v1, v2); - - super.LayoutSubviews (); - Assert.Equal (new (0, 0, 10, 10), v1.Frame); - Assert.Equal (new (10, 10, 10, 10), v2.Frame); - super.Dispose (); - } -} diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index 1f11997b81..27efad8359 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -4,12 +4,11 @@ namespace Terminal.Gui.LayoutTests; -[Trait("Category", "Layout")] +[Trait ("Category", "Layout")] public partial class DimAutoTests (ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; - [SetupFakeDriver] [Fact] public void Change_To_Non_Auto_Resets_ContentSize () { @@ -26,6 +25,7 @@ public void Change_To_Non_Auto_Resets_ContentSize () // Change text to a longer string view.Text = "0123456789"; + view.Layout (new (100, 100)); Assert.Equal (new (0, 0, 10, 1), view.Frame); Assert.Equal (new (10, 1), view.GetContentSize ()); @@ -33,6 +33,7 @@ public void Change_To_Non_Auto_Resets_ContentSize () view.Width = 5; view.Height = 1; + view.SetRelativeLayout (new (100, 100)); Assert.Equal (new (5, 1), view.GetContentSize ()); } @@ -216,7 +217,7 @@ public void TestEquality () Style: DimAutoStyle.Auto ); - var c = new DimAuto( + var c = new DimAuto ( MaximumContentDim: 2, MinimumContentDim: 1, Style: DimAutoStyle.Auto @@ -966,9 +967,9 @@ public void DimAutoStyle_Content_IgnoresText_WhenContentSizeNotSet () [Fact] public void DimAutoStyle_Content_UsesLargestSubview_WhenContentSizeNotSet () { - var view = new View (); - view.Add (new View { Frame = new (0, 0, 5, 5) }); // Smaller subview - view.Add (new View { Frame = new (0, 0, 10, 10) }); // Larger subview + var view = new View { Id = "view" }; + view.Add (new View { Id = "smaller", Frame = new (0, 0, 5, 5) }); // Smaller subview + view.Add (new View { Id = "larger", Frame = new (0, 0, 10, 10) }); // Larger subview Dim dim = Auto (DimAutoStyle.Content); @@ -990,6 +991,8 @@ public void DimAutoStyle_Content_UsesContentSize_If_No_Subviews () [Fact] public void DimAutoStyle_Content_Pos_AnchorEnd_Locates_Correctly () { + Application.SetScreenSize (new Size (10, 10)); + DimAutoTestView view = new (Auto (DimAutoStyle.Content), Auto (DimAutoStyle.Content)); View subView = new () @@ -999,24 +1002,23 @@ public void DimAutoStyle_Content_Pos_AnchorEnd_Locates_Correctly () }; view.Add (subView); - view.SetRelativeLayout (new (10, 10)); + view.Layout (); Assert.Equal (new (5, 1), view.Frame.Size); Assert.Equal (new (0, 0), view.Frame.Location); view.X = 0; - view.Y = Pos.AnchorEnd (1); - view.SetRelativeLayout (new (10, 10)); + view.Layout (); Assert.Equal (new (5, 1), view.Frame.Size); Assert.Equal (new (0, 9), view.Frame.Location); view.Y = Pos.AnchorEnd (); - view.SetRelativeLayout (new (10, 10)); + view.Layout (); Assert.Equal (new (5, 1), view.Frame.Size); Assert.Equal (new (0, 9), view.Frame.Location); view.Y = Pos.AnchorEnd () - 1; - view.SetRelativeLayout (new (10, 10)); + view.Layout (); Assert.Equal (new (5, 1), view.Frame.Size); Assert.Equal (new (0, 8), view.Frame.Location); } diff --git a/UnitTests/View/Layout/LayoutTests.cs b/UnitTests/View/Layout/LayoutTests.cs index a22c113d39..d0482654ce 100644 --- a/UnitTests/View/Layout/LayoutTests.cs +++ b/UnitTests/View/Layout/LayoutTests.cs @@ -6,6 +6,56 @@ public class LayoutTests (ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; + + [Fact] + [AutoInitShutdown] + public void Screen_Size_Change_Causes_Layout () + { + Application.Top = new (); + + var view = new View + { + X = 3, + Y = 2, + Width = 10, + Height = 1, + Text = "0123456789" + }; + Application.Top.Add (view); + + var rs = Application.Begin (Application.Top); + + Assert.Equal (new (0, 0, 80, 25), new Rectangle (0, 0, View.Driver.Cols, View.Driver.Rows)); + Assert.Equal (new (0, 0, View.Driver.Cols, View.Driver.Rows), Application.Top.Frame); + Assert.Equal (new (0, 0, 80, 25), Application.Top.Frame); + + ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); + Assert.Equal (new (0, 0, View.Driver.Cols, View.Driver.Rows), Application.Top.Frame); + + Assert.Equal (new (0, 0, 20, 10), Application.Top.Frame); + + Application.End (rs); + + } + + [Fact] + [TestRespondersDisposed] + public void LayoutSubviews () + { + var superRect = new Rectangle (0, 0, 100, 100); + var super = new View { Frame = superRect, Text = "super" }; + var v1 = new View { X = 0, Y = 0, Width = 10, Height = 10 }; + + var v2 = new View { X = 10, Y = 10, Width = 10, Height = 10 }; + + super.Add (v1, v2); + + super.LayoutSubviews (); + Assert.Equal (new (0, 0, 10, 10), v1.Frame); + Assert.Equal (new (10, 10, 10, 10), v2.Frame); + super.Dispose (); + } + [Fact] public void LayoutSubviews_No_SuperView () { @@ -35,7 +85,7 @@ public void LayoutSubviews_No_SuperView () } [Fact] - public void Add_Does_Not_Call_LayoutSubviews () + public void Add_Does_Not_Call_Layout () { var superView = new View { Id = "superView" }; var view = new View { Id = "view" }; @@ -49,10 +99,7 @@ public void Add_Does_Not_Call_LayoutSubviews () Assert.False (layoutStartedRaised); Assert.False (layoutCompleteRaised); - superView.Remove(view); - - superView.BeginInit(); - superView.EndInit (); + superView.Remove (view); superView.Add (view); @@ -61,20 +108,6 @@ public void Add_Does_Not_Call_LayoutSubviews () } - [Fact] - public void BeginEndInit_Do_Not_Call_LayoutSubviews () - { - var superView = new View { Id = "superView" }; - bool layoutStartedRaised = false; - bool layoutCompleteRaised = false; - superView.LayoutStarted += (sender, e) => layoutStartedRaised = true; - superView.LayoutComplete += (sender, e) => layoutCompleteRaised = true; - superView.BeginInit (); - superView.EndInit (); - Assert.False (layoutStartedRaised); - Assert.False (layoutCompleteRaised); - } - [Fact] public void LayoutSubViews_Raises_LayoutStarted_LayoutComplete () { @@ -203,38 +236,376 @@ public void LayoutSubviews_LayoutStarted_Complete () { var superView = new View (); var view = new View (); + + var layoutStartedCount = 0; + var layoutCompleteCount = 0; + + var borderLayoutStartedCount = 0; + var borderLayoutCompleteCount = 0; + + view.LayoutStarted += (sender, e) => layoutStartedCount++; + view.LayoutComplete += (sender, e) => layoutCompleteCount++; + + view.Border.LayoutStarted += (sender, e) => borderLayoutStartedCount++; + view.Border.LayoutComplete += (sender, e) => borderLayoutCompleteCount++; + + superView.Add (view); + Assert.Equal (0, borderLayoutStartedCount); + Assert.Equal (0, borderLayoutCompleteCount); + Assert.Equal (0, layoutStartedCount); + Assert.Equal (0, layoutCompleteCount); + superView.BeginInit (); + Assert.Equal (0, borderLayoutStartedCount); + Assert.Equal (0, borderLayoutCompleteCount); + Assert.Equal (0, layoutStartedCount); + Assert.Equal (0, layoutCompleteCount); + superView.EndInit (); + Assert.Equal (1, borderLayoutStartedCount); + Assert.Equal (1, borderLayoutCompleteCount); + Assert.Equal (1, layoutStartedCount); + Assert.Equal (1, layoutCompleteCount); - var layoutStarted = false; - var layoutComplete = false; + superView.LayoutSubviews (); + Assert.Equal (1, borderLayoutStartedCount); + Assert.Equal (1, borderLayoutCompleteCount); + Assert.Equal (1, layoutStartedCount); + Assert.Equal (1, layoutCompleteCount); - var borderLayoutStarted = false; - var borderLayoutComplete = false; + superView.SetLayoutNeeded (); + superView.LayoutSubviews (); + Assert.Equal (2, borderLayoutStartedCount); + Assert.Equal (2, borderLayoutCompleteCount); + Assert.Equal (2, layoutStartedCount); + Assert.Equal (2, layoutCompleteCount); + + superView.Dispose (); + } - view.LayoutStarted += (sender, e) => layoutStarted = true; - view.LayoutComplete += (sender, e) => layoutComplete = true; + [Fact] + public void LayoutSubviews__Honors_IsLayoutNeeded () + { + var superView = new View (); + var view = new View (); + + var layoutStartedCount = 0; + var layoutCompleteCount = 0; + + var borderLayoutStartedCount = 0; + var borderLayoutCompleteCount = 0; + + view.LayoutStarted += (sender, e) => layoutStartedCount++; + view.LayoutComplete += (sender, e) => layoutCompleteCount++; + + view.Border.LayoutStarted += (sender, e) => borderLayoutStartedCount++; + view.Border.LayoutComplete += (sender, e) => borderLayoutCompleteCount++; + + + superView.Add (view); - view.Border.LayoutStarted += (sender, e) => - { - Assert.True (layoutStarted); - borderLayoutStarted = true; - }; - view.Border.LayoutComplete += (sender, e) => - { - Assert.True (layoutStarted); - Assert.False (layoutComplete); - borderLayoutComplete = true; - }; + superView.LayoutSubviews (); + Assert.Equal (1, borderLayoutStartedCount); + Assert.Equal (1, borderLayoutCompleteCount); + Assert.Equal (1, layoutStartedCount); + Assert.Equal (1, layoutCompleteCount); superView.LayoutSubviews (); + Assert.Equal (1, borderLayoutStartedCount); + Assert.Equal (1, borderLayoutCompleteCount); + Assert.Equal (1, layoutStartedCount); + Assert.Equal (1, layoutCompleteCount); - Assert.True (borderLayoutStarted); - Assert.True (borderLayoutComplete); + superView.SetLayoutNeeded (); + superView.LayoutSubviews (); + Assert.Equal (2, borderLayoutStartedCount); + Assert.Equal (2, borderLayoutCompleteCount); + Assert.Equal (2, layoutStartedCount); + Assert.Equal (2, layoutCompleteCount); - Assert.True (layoutStarted); - Assert.True (layoutComplete); superView.Dispose (); } + + [Fact] + public void Set_X_Does_Not_Change_Frame_Until_Layout () + { + var v = new View (); + Assert.Equal (0, v.Frame.X); + + v.Layout (); + Assert.Equal (0, v.Frame.X); + + v.X = 1; + Assert.Equal (0, v.Frame.X); + + v.Layout (); + Assert.Equal (1, v.Frame.X); + + v.X = 2; + Assert.Equal (1, v.Frame.X); + + v.Layout (); + Assert.Equal (2, v.Frame.X); + } + + + [Fact] + public void Set_Y_Does_Not_Change_Frame_Until_Layout () + { + var v = new View (); + Assert.Equal (0, v.Frame.Y); + + v.Layout (); + Assert.Equal (0, v.Frame.Y); + + v.Y = 1; + Assert.Equal (0, v.Frame.Y); + + v.Layout (); + Assert.Equal (1, v.Frame.Y); + + v.Y = 2; + Assert.Equal (1, v.Frame.Y); + + v.Layout (); + Assert.Equal (2, v.Frame.Y); + } + + + [Fact] + public void Set_Width_Does_Not_Change_Frame_Until_Layout () + { + var v = new View (); + Assert.Equal (0, v.Frame.Width); + + v.Layout (); + Assert.Equal (0, v.Frame.Width); + + v.Width = 1; + Assert.Equal (0, v.Frame.Width); + + v.Layout (); + Assert.Equal (1, v.Frame.Width); + + v.Width = 2; + Assert.Equal (1, v.Frame.Width); + + v.Layout (); + Assert.Equal (2, v.Frame.Width); + } + + + [Fact] + public void Set_Height_Does_Not_Change_Frame_Until_Layout () + { + var v = new View (); + Assert.Equal (0, v.Frame.Height); + + v.Layout (); + Assert.Equal (0, v.Frame.Height); + + v.Height = 1; + Assert.Equal (0, v.Frame.Height); + + v.Layout (); + Assert.Equal (1, v.Frame.Height); + + v.Height = 2; + Assert.Equal (1, v.Frame.Height); + + v.Layout (); + Assert.Equal (2, v.Frame.Height); + } + + [Fact] + [TestRespondersDisposed] + public void Change_Height_or_Width_MakesComputed () + { + var v = new View { Frame = Rectangle.Empty }; + v.Height = Dim.Fill (); + v.Width = Dim.Fill (); + v.Dispose (); + } + + [Fact] + [TestRespondersDisposed] + public void Change_X_or_Y_Absolute () + { + var frame = new Rectangle (1, 2, 3, 4); + var newFrame = new Rectangle (10, 20, 3, 4); + + var v = new View { Frame = frame }; + v.X = newFrame.X; + v.Y = newFrame.Y; + v.Layout (); + Assert.Equal (newFrame, v.Frame); + + Assert.Equal ( + new (0, 0, newFrame.Width, newFrame.Height), + v.Viewport + ); // With Absolute Viewport *is* deterministic before Layout + Assert.Equal ($"Absolute({newFrame.X})", v.X.ToString ()); + Assert.Equal ($"Absolute({newFrame.Y})", v.Y.ToString ()); + Assert.Equal (Dim.Absolute (3), v.Width); + Assert.Equal (Dim.Absolute (4), v.Height); + v.Dispose (); + } + + [Fact] + [TestRespondersDisposed] + public void Change_X_or_Y_MakesComputed () + { + var v = new View { Frame = Rectangle.Empty }; + v.X = Pos.Center (); + v.Y = Pos.Center (); + v.Dispose (); + } + + [Fact] + [TestRespondersDisposed] + public void Change_X_Y_Height_Width_Absolute () + { + var v = new View { Frame = Rectangle.Empty }; + v.X = 1; + v.Y = 2; + v.Height = 3; + v.Width = 4; + v.Dispose (); + + v = new () { Frame = Rectangle.Empty }; + v.X = Pos.Center (); + v.Y = Pos.Center (); + v.Width = Dim.Fill (); + v.Height = Dim.Fill (); + v.Dispose (); + + v = new () { Frame = Rectangle.Empty }; + v.X = Pos.Center (); + v.Y = Pos.Center (); + v.Width = Dim.Fill (); + v.Height = Dim.Fill (); + + v.X = 1; + v.Dispose (); + + v = new () { Frame = Rectangle.Empty }; + v.X = Pos.Center (); + v.Y = Pos.Center (); + v.Width = Dim.Fill (); + v.Height = Dim.Fill (); + + v.Y = 2; + v.Dispose (); + + v = new () { Frame = Rectangle.Empty }; + v.X = Pos.Center (); + v.Y = Pos.Center (); + v.Width = Dim.Fill (); + v.Height = Dim.Fill (); + + v.Width = 3; + v.Dispose (); + + v = new () { Frame = Rectangle.Empty }; + v.X = Pos.Center (); + v.Y = Pos.Center (); + v.Width = Dim.Fill (); + v.Height = Dim.Fill (); + + v.Height = 3; + v.Dispose (); + + v = new () { Frame = Rectangle.Empty }; + v.X = Pos.Center (); + v.Y = Pos.Center (); + v.Width = Dim.Fill (); + v.Height = Dim.Fill (); + + v.X = 1; + v.Y = 2; + v.Height = 3; + v.Width = 4; + v.Dispose (); + } + + [Fact] + public void Constructor () + { + var v = new View (); + v.Dispose (); + + var frame = Rectangle.Empty; + v = new () { Frame = frame }; + v.Layout (); + Assert.Equal (frame, v.Frame); + + Assert.Equal ( + new (0, 0, frame.Width, frame.Height), + v.Viewport + ); // With Absolute Viewport *is* deterministic before Layout + Assert.Equal (Pos.Absolute (0), v.X); + Assert.Equal (Pos.Absolute (0), v.Y); + Assert.Equal (Dim.Absolute (0), v.Width); + Assert.Equal (Dim.Absolute (0), v.Height); + v.Dispose (); + + frame = new (1, 2, 3, 4); + v = new () { Frame = frame }; + v.Layout (); + Assert.Equal (frame, v.Frame); + Assert.Equal ( + new (0, 0, frame.Width, frame.Height), + v.Viewport + ); // With Absolute Viewport *is* deterministic before Layout + Assert.Equal (Pos.Absolute (1), v.X); + Assert.Equal (Pos.Absolute (2), v.Y); + Assert.Equal (Dim.Absolute (3), v.Width); + Assert.Equal (Dim.Absolute (4), v.Height); + v.Dispose (); + + v = new () { Frame = frame, Text = "v" }; + v.Layout (); + Assert.Equal (frame, v.Frame); + Assert.Equal ( + new (0, 0, frame.Width, frame.Height), + v.Viewport + ); // With Absolute Viewport *is* deterministic before Layout + Assert.Equal (Pos.Absolute (1), v.X); + Assert.Equal (Pos.Absolute (2), v.Y); + Assert.Equal (Dim.Absolute (3), v.Width); + Assert.Equal (Dim.Absolute (4), v.Height); + v.Dispose (); + + v = new () { X = frame.X, Y = frame.Y, Text = "v" }; + v.Layout (); + Assert.Equal (new (frame.X, frame.Y, 0, 0), v.Frame); + Assert.Equal (new (0, 0, 0, 0), v.Viewport); // With Absolute Viewport *is* deterministic before Layout + Assert.Equal (Pos.Absolute (1), v.X); + Assert.Equal (Pos.Absolute (2), v.Y); + Assert.Equal (Dim.Absolute (0), v.Width); + Assert.Equal (Dim.Absolute (0), v.Height); + v.Dispose (); + + v = new (); + v.Layout (); + Assert.Equal (new (0, 0, 0, 0), v.Frame); + Assert.Equal (new (0, 0, 0, 0), v.Viewport); // With Absolute Viewport *is* deterministic before Layout + Assert.Equal (Pos.Absolute (0), v.X); + Assert.Equal (Pos.Absolute (0), v.Y); + Assert.Equal (Dim.Absolute (0), v.Width); + Assert.Equal (Dim.Absolute (0), v.Height); + v.Dispose (); + + v = new () { X = frame.X, Y = frame.Y, Width = frame.Width, Height = frame.Height }; + v.Layout (); + Assert.Equal (new (frame.X, frame.Y, 3, 4), v.Frame); + Assert.Equal (new (0, 0, 3, 4), v.Viewport); // With Absolute Viewport *is* deterministic before Layout + Assert.Equal (Pos.Absolute (1), v.X); + Assert.Equal (Pos.Absolute (2), v.Y); + Assert.Equal (Dim.Absolute (3), v.Width); + Assert.Equal (Dim.Absolute (4), v.Height); + v.Dispose (); + } + } diff --git a/UnitTests/View/Layout/Pos.CenterTests.cs b/UnitTests/View/Layout/Pos.CenterTests.cs index ccd6ba5bda..a27dc5e228 100644 --- a/UnitTests/View/Layout/Pos.CenterTests.cs +++ b/UnitTests/View/Layout/Pos.CenterTests.cs @@ -94,7 +94,7 @@ public void PosCenter_SubView_85_Percent_Height (int height) var firstIteration = false; ((FakeDriver)Application.Driver!).SetBufferSize (20, height); - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; switch (height) @@ -241,7 +241,7 @@ public void PosCenter_SubView_85_Percent_Width (int width) var firstIteration = false; ((FakeDriver)Application.Driver!).SetBufferSize (width, 7); - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; switch (width) diff --git a/UnitTests/View/Layout/ToScreenTests.cs b/UnitTests/View/Layout/ToScreenTests.cs index 17153badc0..3e724f0ed0 100644 --- a/UnitTests/View/Layout/ToScreenTests.cs +++ b/UnitTests/View/Layout/ToScreenTests.cs @@ -960,7 +960,7 @@ public void ScreenToView_ViewToScreen_GetViewsUnderMouse_Full_Top () }; Application.Top.Add (view); - Application.Begin (Application.Top); + var rs = Application.Begin (Application.Top); Assert.Equal (new (0, 0, 80, 25), new Rectangle (0, 0, View.Driver.Cols, View.Driver.Rows)); Assert.Equal (new (0, 0, View.Driver.Cols, View.Driver.Rows), Application.Top.Frame); @@ -970,6 +970,7 @@ public void ScreenToView_ViewToScreen_GetViewsUnderMouse_Full_Top () Assert.Equal (new (0, 0, View.Driver.Cols, View.Driver.Rows), Application.Top.Frame); Assert.Equal (new (0, 0, 20, 10), Application.Top.Frame); + _ = TestHelpers.AssertDriverContentsWithFrameAre ( @" ┌──────────────────┐ diff --git a/UnitTests/View/Mouse/MouseTests.cs b/UnitTests/View/Mouse/MouseTests.cs index c4b10fe956..ab23aead12 100644 --- a/UnitTests/View/Mouse/MouseTests.cs +++ b/UnitTests/View/Mouse/MouseTests.cs @@ -103,12 +103,15 @@ public void ButtonPressed_In_Border_Starts_Drag (int marginThickness, int border var top = new Toplevel (); top.Add (testView); - Application.Begin (top); + + var rs = Application.Begin (top); + Assert.Equal (4, testView.Frame.X); Assert.Equal (new Point (4, 4), testView.Frame.Location); Application.RaiseMouseEvent (new () { ScreenPosition = new (xy, xy), Flags = MouseFlags.Button1Pressed }); Application.RaiseMouseEvent (new () { ScreenPosition = new (xy + 1, xy + 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); + Application.RunIteration(ref rs, false); Assert.Equal (expectedMoved, new Point (5, 5) == testView.Frame.Location); top.Dispose (); diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index a6cd276709..3c10f3d726 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -1025,7 +1025,7 @@ public void Visible_Clear_The_View_Output () view.Visible = false; var firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ( @" diff --git a/UnitTests/Views/ContextMenuTests.cs b/UnitTests/Views/ContextMenuTests.cs index 1bd08ed724..0b5d9a52ef 100644 --- a/UnitTests/Views/ContextMenuTests.cs +++ b/UnitTests/Views/ContextMenuTests.cs @@ -151,7 +151,7 @@ public void Draw_A_ContextMenu_Over_A_Borderless_Top () Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (8, 2), Flags = MouseFlags.Button3Clicked }); var firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -234,7 +234,7 @@ public void Draw_A_ContextMenu_Over_A_Dialog () Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (9, 3), Flags = MouseFlags.Button3Clicked }); var firstIteration = false; - Application.RunIteration (ref rsDialog, ref firstIteration); + Application.RunIteration (ref rsDialog, firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -290,7 +290,7 @@ public void Draw_A_ContextMenu_Over_A_Top_Dialog () Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (9, 3), Flags = MouseFlags.Button3Clicked }); var firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -1238,7 +1238,7 @@ public void UseSubMenusSingleFrame_True_By_Mouse () Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (6, 13), Flags = MouseFlags.Button1Clicked }); var firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.Subviews [0].Frame); Assert.Equal (new Rectangle (5, 11, 15, 6), Application.Top.Subviews [1].Frame); @@ -1256,7 +1256,7 @@ public void UseSubMenusSingleFrame_True_By_Mouse () Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (6, 12), Flags = MouseFlags.Button1Clicked }); firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.Subviews [0].Frame); TestHelpers.AssertDriverContentsWithFrameAre ( @@ -1330,7 +1330,7 @@ public void UseSubMenusSingleFrame_False_By_Mouse () Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (6, 13), Flags = MouseFlags.ReportMousePosition }); var firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.Subviews [0].Frame); TestHelpers.AssertDriverContentsWithFrameAre ( @@ -1347,7 +1347,7 @@ public void UseSubMenusSingleFrame_False_By_Mouse () Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (6, 14), Flags = MouseFlags.ReportMousePosition }); firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.Subviews [0].Frame); TestHelpers.AssertDriverContentsWithFrameAre ( @@ -1365,7 +1365,7 @@ public void UseSubMenusSingleFrame_False_By_Mouse () Application.RaiseMouseEvent (new MouseEventArgs { ScreenPosition = new (6, 13), Flags = MouseFlags.ReportMousePosition }); firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.Subviews [0].Frame); TestHelpers.AssertDriverContentsWithFrameAre ( diff --git a/UnitTests/Views/MenuBarTests.cs b/UnitTests/Views/MenuBarTests.cs index 7ef68bfe21..c2ba406f71 100644 --- a/UnitTests/Views/MenuBarTests.cs +++ b/UnitTests/Views/MenuBarTests.cs @@ -591,7 +591,7 @@ void ChangeMenuTitle (string title) Assert.Equal ("File", menu.Menus [0].Title); menu.OpenMenu (); var firstIteration = false; - Application.RunIteration (ref rsDialog, ref firstIteration); + Application.RunIteration (ref rsDialog, firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -619,7 +619,7 @@ void ChangeMenuTitle (string title) // Need to fool MainLoop into thinking it's running Application.MainLoop.Running = true; - Application.RunIteration (ref rsDialog, ref firstIteration); + Application.RunIteration (ref rsDialog, firstIteration); Assert.Equal (items [0], menu.Menus [0].Title); TestHelpers.AssertDriverContentsWithFrameAre ( @@ -649,14 +649,14 @@ void ChangeMenuTitle (string title) Application.RaiseMouseEvent (new () { ScreenPosition = new (20, 5 + i), Flags = MouseFlags.Button1Clicked }); firstIteration = false; - Application.RunIteration (ref rsDialog, ref firstIteration); + Application.RunIteration (ref rsDialog, firstIteration); Assert.Equal (items [i], menu.Menus [0].Title); } ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); menu.OpenMenu (); firstIteration = false; - Application.RunIteration (ref rsDialog, ref firstIteration); + Application.RunIteration (ref rsDialog, firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -792,7 +792,7 @@ void ChangeMenuTitle (string title) Assert.Equal ("File", menu.Menus [0].Title); menu.OpenMenu (); var firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -815,7 +815,7 @@ void ChangeMenuTitle (string title) // Need to fool MainLoop into thinking it's running Application.MainLoop.Running = true; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); Assert.Equal (items [0], menu.Menus [0].Title); TestHelpers.AssertDriverContentsWithFrameAre ( @@ -834,14 +834,14 @@ void ChangeMenuTitle (string title) Application.RaiseMouseEvent (new () { ScreenPosition = new (20, 5 + i), Flags = MouseFlags.Button1Clicked }); firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); Assert.Equal (items [i], menu.Menus [0].Title); } ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); menu.OpenMenu (); firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -2864,7 +2864,7 @@ public void Resizing_Close_Menus () menu.OpenMenu (); var firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -2877,7 +2877,7 @@ public void Resizing_Close_Menus () ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ( @" diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs index 478e1d00b6..216b10fd02 100644 --- a/UnitTests/Views/TextFieldTests.cs +++ b/UnitTests/Views/TextFieldTests.cs @@ -1081,8 +1081,7 @@ public void Selected_Text_Shows () _textField.CursorPosition = 0; _textField.NewKeyDownEvent (Key.CursorRight.WithCtrl.WithShift); - var first = true; - Application.RunIteration (ref rs, ref first); + Application.RunIteration (ref rs); Assert.Equal (4, _textField.CursorPosition); // TAB to jump between text fields. diff --git a/UnitTests/Views/TextViewTests.cs b/UnitTests/Views/TextViewTests.cs index 7dc1f98586..3e574d843d 100644 --- a/UnitTests/Views/TextViewTests.cs +++ b/UnitTests/Views/TextViewTests.cs @@ -6364,8 +6364,7 @@ public void Selected_Text_Shows () _textView.NewKeyDownEvent (Key.CursorRight.WithCtrl.WithShift); - var first = true; - Application.RunIteration (ref rs, ref first); + Application.RunIteration (ref rs, true); Assert.Equal (new Point (4, 0), _textView.CursorPosition); // TAB to jump between text fields. diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index 6bf764de6a..6663a4ebcf 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -885,7 +885,7 @@ public void Modal_As_Top_Will_Drag_Cleanly () Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed }); var firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); Assert.Equal (window.Border, Application.MouseGrabView); Assert.Equal (new (0, 0, 10, 3), window.Frame); @@ -897,7 +897,7 @@ public void Modal_As_Top_Will_Drag_Cleanly () }); firstIteration = false; - Application.RunIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, firstIteration); Assert.Equal (window.Border, Application.MouseGrabView); Assert.Equal (new (1, 1, 10, 3), window.Frame); From ed80c66e13dcfdc823052deab563a569bb031085 Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 17 Oct 2024 12:10:41 -0600 Subject: [PATCH 005/118] Fixed content scrolling --- Terminal.Gui/View/Adornment/Margin.cs | 68 ++++++++++++++----------- Terminal.Gui/View/View.Drawing.cs | 5 ++ Terminal.Gui/View/View.Layout.cs | 4 +- UICatalog/Scenarios/ContentScrolling.cs | 29 ++++++----- 4 files changed, 61 insertions(+), 45 deletions(-) diff --git a/Terminal.Gui/View/Adornment/Margin.cs b/Terminal.Gui/View/Adornment/Margin.cs index eff603e296..97dc0f093b 100644 --- a/Terminal.Gui/View/Adornment/Margin.cs +++ b/Terminal.Gui/View/Adornment/Margin.cs @@ -43,27 +43,6 @@ public override void BeginInit () } ShadowStyle = base.ShadowStyle; - - Add ( - _rightShadow = new () - { - X = Pos.AnchorEnd (1), - Y = 0, - Width = 1, - Height = Dim.Fill (), - ShadowStyle = ShadowStyle, - Orientation = Orientation.Vertical - }, - _bottomShadow = new () - { - X = 0, - Y = Pos.AnchorEnd (1), - Width = Dim.Fill (), - Height = 1, - ShadowStyle = ShadowStyle, - Orientation = Orientation.Horizontal - } - ); } /// @@ -135,9 +114,18 @@ public override void OnDrawContent (Rectangle viewport) /// public ShadowStyle SetShadow (ShadowStyle style) { - if (ShadowStyle == style) + if (_rightShadow is { }) { - // return style; + Remove (_rightShadow); + _rightShadow.Dispose (); + _rightShadow = null; + } + + if (_bottomShadow is { }) + { + Remove (_bottomShadow); + _bottomShadow.Dispose (); + _bottomShadow = null; } if (ShadowStyle != ShadowStyle.None) @@ -152,14 +140,28 @@ public ShadowStyle SetShadow (ShadowStyle style) Thickness = new (Thickness.Left, Thickness.Top, Thickness.Right + 1, Thickness.Bottom + 1); } - if (_rightShadow is { }) - { - _rightShadow.ShadowStyle = style; - } - - if (_bottomShadow is { }) + if (style != ShadowStyle.None) { - _bottomShadow.ShadowStyle = style; + _rightShadow = new () + { + X = Pos.AnchorEnd (1), + Y = 0, + Width = 1, + Height = Dim.Fill (), + ShadowStyle = style, + Orientation = Orientation.Vertical + }; + + _bottomShadow = new () + { + X = 0, + Y = Pos.AnchorEnd (1), + Width = Dim.Fill (), + Height = 1, + ShadowStyle = style, + Orientation = Orientation.Horizontal + }; + Add (_rightShadow, _bottomShadow); } return style; @@ -169,7 +171,11 @@ public ShadowStyle SetShadow (ShadowStyle style) public override ShadowStyle ShadowStyle { get => base.ShadowStyle; - set => base.ShadowStyle = SetShadow (value); + set + { + base.ShadowStyle = SetShadow (value); + + } } private const int PRESS_MOVE_HORIZONTAL = 1; diff --git a/Terminal.Gui/View/View.Drawing.cs b/Terminal.Gui/View/View.Drawing.cs index 9099c406a5..f55dcc8679 100644 --- a/Terminal.Gui/View/View.Drawing.cs +++ b/Terminal.Gui/View/View.Drawing.cs @@ -700,6 +700,11 @@ public void SetNeedsDisplay (Rectangle region) SuperView?.SetSubViewNeedsDisplay (); + if (this is Adornment adornment) + { + adornment.Parent?.SetSubViewNeedsDisplay (); + } + foreach (View subview in Subviews) { if (subview.Frame.IntersectsWith (region)) diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs index af6dad65e5..3625344ae5 100644 --- a/Terminal.Gui/View/View.Layout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -822,12 +822,12 @@ public void SetLayoutNeeded () TextFormatter.NeedsFormat = true; + SuperView?.SetLayoutNeeded (); + if (this is Adornment adornment) { adornment.Parent?.SetLayoutNeeded (); } - - SuperView?.SetLayoutNeeded (); } /// diff --git a/UICatalog/Scenarios/ContentScrolling.cs b/UICatalog/Scenarios/ContentScrolling.cs index b6986067ec..67b3c8967d 100644 --- a/UICatalog/Scenarios/ContentScrolling.cs +++ b/UICatalog/Scenarios/ContentScrolling.cs @@ -47,7 +47,7 @@ public ScrollingDemoView () // Add a status label to the border that shows Viewport and ContentSize values. Bit of a hack. // TODO: Move to Padding with controls Border.Add (new Label { X = 20 }); - LayoutComplete += VirtualDemoView_LayoutComplete; + ViewportChanged += VirtualDemoView_LayoutComplete; MouseEvent += VirtualDemoView_MouseEvent; } @@ -81,18 +81,14 @@ private void VirtualDemoView_MouseEvent (object sender, MouseEventArgs e) } } - private void VirtualDemoView_LayoutComplete (object sender, LayoutEventArgs e) + private void VirtualDemoView_LayoutComplete (object sender, DrawEventArgs drawEventArgs) { - Label status = Border.Subviews.OfType