From 73278bb8934ca72e127dbde53f98ee51c82dd7a7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 24 Jul 2025 19:48:28 +0100 Subject: [PATCH 01/34] Fixes #4208. MainLoopSyncContext doesn't work with the v2 drivers --- Terminal.Gui/App/MainLoopSyncContext.cs | 23 +++-- .../Application/SynchronizatonContextTests.cs | 92 ++++++++++++------- 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/Terminal.Gui/App/MainLoopSyncContext.cs b/Terminal.Gui/App/MainLoopSyncContext.cs index 548722d6e2..2f188b5e08 100644 --- a/Terminal.Gui/App/MainLoopSyncContext.cs +++ b/Terminal.Gui/App/MainLoopSyncContext.cs @@ -11,15 +11,22 @@ internal sealed class MainLoopSyncContext : SynchronizationContext public override void Post (SendOrPostCallback d, object state) { // Queue the task - Application.MainLoop?.TimedEvents.Add (TimeSpan.Zero, - () => - { - d (state); + if (ApplicationImpl.Instance.IsLegacy) + { + Application.MainLoop?.TimedEvents.Add (TimeSpan.Zero, + () => + { + d (state); - return false; - } - ); - Application.MainLoop?.Wakeup (); + return false; + } + ); + Application.MainLoop?.Wakeup (); + } + else + { + ApplicationImpl.Instance.Invoke (() => { d (state); }); + } } //_mainLoop.Driver.Wakeup (); diff --git a/Tests/UnitTests/Application/SynchronizatonContextTests.cs b/Tests/UnitTests/Application/SynchronizatonContextTests.cs index 0e878983b0..86f1380279 100644 --- a/Tests/UnitTests/Application/SynchronizatonContextTests.cs +++ b/Tests/UnitTests/Application/SynchronizatonContextTests.cs @@ -21,43 +21,73 @@ public void SynchronizationContext_CreateCopy () Application.Shutdown (); } + private object _lockPost = new (); + [Theory] [InlineData (typeof (FakeDriver))] [InlineData (typeof (NetDriver))] [InlineData (typeof (WindowsDriver))] [InlineData (typeof (CursesDriver))] - public void SynchronizationContext_Post (Type driverType) + [InlineData (typeof (ConsoleDriverFacade), "v2win")] + [InlineData (typeof (ConsoleDriverFacade), "v2net")] + public void SynchronizationContext_Post (Type driverType, string driverName = null) { - ConsoleDriver.RunningUnitTests = true; - Application.Init (driverName: driverType.Name); - SynchronizationContext context = SynchronizationContext.Current; - - var success = false; - - Task.Run ( - () => - { - Thread.Sleep (500); - - // non blocking - context.Post ( - delegate - { - success = true; - - // then tell the application to quit - Application.Invoke (() => Application.RequestStop ()); - }, - null - ); - Assert.False (success); - } - ); - - // blocks here until the RequestStop is processed at the end of the test - Application.Run ().Dispose (); - Assert.True (success); - Application.Shutdown (); + lock (_lockPost) + { + ConsoleDriver.RunningUnitTests = true; + + if (driverType.Name.Contains ("ConsoleDriverFacade")) + { + Application.Init (driverName: driverName); + } + else + { + Application.Init (driverName: driverType.Name); + } + + SynchronizationContext context = SynchronizationContext.Current; + + var success = false; + + Task.Run (() => + { + while (Application.Top is null || Application.Top is { Running: false }) + { + Thread.Sleep (500); + } + + // non blocking + context.Post ( + delegate + { + success = true; + + // then tell the application to quit + Application.Invoke (() => Application.RequestStop ()); + }, + null + ); + + if (Application.Top is { Running: true }) + { + Assert.False (success); + } + } + ); + + // blocks here until the RequestStop is processed at the end of the test + Application.Run ().Dispose (); + Assert.True (success); + + if (ApplicationImpl.Instance is ApplicationV2) + { + ApplicationImpl.Instance.Shutdown (); + } + else + { + Application.Shutdown (); + } + } } [Fact] From b6d0aefd9b268e73eaa132a576a568498f2fe5ea Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 25 Jul 2025 03:06:49 +0100 Subject: [PATCH 02/34] Fixes #3951. Add DimFuncWithView with a View dependency --- .../Scenarios/CharacterMap/CharacterMap.cs | 3 +- Terminal.Gui/App/Application.Run.cs | 5 +- Terminal.Gui/ViewBase/Layout/Dim.cs | 10 ++++ .../ViewBase/Layout/DimFuncWithView.cs | 35 +++++++++++ Terminal.Gui/ViewBase/View.cs | 6 -- .../View/Adornment/ShadowStyleTests.cs | 1 + .../UnitTests/View/Draw/ClearViewportTests.cs | 5 +- Tests/UnitTests/View/Draw/ClipTests.cs | 4 +- .../View/Layout/GetViewsUnderLocationTests.cs | 3 + Tests/UnitTests/View/Mouse/MouseTests.cs | 1 + Tests/UnitTests/View/TextTests.cs | 2 + Tests/UnitTests/View/ViewTests.cs | 1 + Tests/UnitTests/Views/ButtonTests.cs | 1 + Tests/UnitTests/Views/ColorPicker16Tests.cs | 2 +- Tests/UnitTests/Views/ColorPickerTests.cs | 2 +- Tests/UnitTests/Views/LabelTests.cs | 2 +- .../UnitTests/Views/Menuv1/MenuBarv1Tests.cs | 1 + Tests/UnitTests/Views/ProgressBarTests.cs | 8 +-- Tests/UnitTests/Views/SliderTests.cs | 3 + Tests/UnitTests/Views/TextFieldTests.cs | 1 + Tests/UnitTests/Views/TileViewTests.cs | 8 +++ .../View/Layout/Dim.AutoTests.PosTypes.cs | 7 +++ .../View/Layout/Dim.FuncWithViewTests.cs | 58 +++++++++++++++++++ .../View/Layout/SetLayoutTests.cs | 30 +++++----- .../View/Layout/SetRelativeLayoutTests.cs | 1 + .../UnitTestsParallelizable/View/TextTests.cs | 1 + .../Views/TextFieldTests.cs | 1 + 27 files changed, 166 insertions(+), 36 deletions(-) create mode 100644 Terminal.Gui/ViewBase/Layout/DimFuncWithView.cs create mode 100644 Tests/UnitTestsParallelizable/View/Layout/Dim.FuncWithViewTests.cs diff --git a/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs b/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs index 4e7679b6bc..1eb68f1d1d 100644 --- a/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs +++ b/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs @@ -39,7 +39,6 @@ public override void Main () { X = 0, Y = 1, - Width = Dim.Fill (Dim.Func (() => _categoryList!.Frame.Width)), Height = Dim.Fill (), // SchemeName = "Base" @@ -172,6 +171,8 @@ public override void Main () }; top.Add (menu); + _charMap.Width = Dim.Fill (Dim.FuncWithView (v => v!.Frame.Width, _categoryList)); + _charMap.SelectedCodePoint = 0; _charMap.SetFocus (); diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index 30c1384a3e..eafe91f141 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -189,7 +189,7 @@ public static RunState Begin (Toplevel toplevel) if (!toplevel.IsInitialized) { toplevel.BeginInit (); - toplevel.EndInit (); // Calls Layout + toplevel.EndInit (); // Calls SetNeedsLayout } // Call ConfigurationManager Apply here to ensure all subscribers to ConfigurationManager.Applied @@ -212,8 +212,7 @@ public static RunState Begin (Toplevel toplevel) NotifyNewRunState?.Invoke (toplevel, new (rs)); - // Force an Idle event so that an Iteration (and Refresh) happen. - Invoke (() => { }); + LayoutAndDraw (true); return rs; } diff --git a/Terminal.Gui/ViewBase/Layout/Dim.cs b/Terminal.Gui/ViewBase/Layout/Dim.cs index 83cc2e42e1..c674d3333b 100644 --- a/Terminal.Gui/ViewBase/Layout/Dim.cs +++ b/Terminal.Gui/ViewBase/Layout/Dim.cs @@ -148,6 +148,16 @@ public abstract record Dim : IEqualityOperators /// The returned from the function. public static Dim Func (Func function) { return new DimFunc (function); } + /// + /// Creates a function object that computes the dimension based on the passed view and by executing + /// the provided function. + /// The function will be called every time the dimension is needed. + /// + /// The function to be executed. + /// The view where the data will be retrieved. + /// The returned from the function based on the passed view. + public static Dim FuncWithView (Func function, View? view) { return new DimFuncWithView (function, view); } + /// Creates a object that tracks the Height of the specified . /// The height of the other . /// The view that will be tracked. diff --git a/Terminal.Gui/ViewBase/Layout/DimFuncWithView.cs b/Terminal.Gui/ViewBase/Layout/DimFuncWithView.cs new file mode 100644 index 0000000000..2ff16bc9f2 --- /dev/null +++ b/Terminal.Gui/ViewBase/Layout/DimFuncWithView.cs @@ -0,0 +1,35 @@ +#nullable enable +namespace Terminal.Gui.ViewBase; + +/// +/// Represents a function object that computes the dimension based on the passed view and by +/// executing the provided function. +/// +/// +/// 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. If this function throws ... +/// The returned from the function based on the passed view. +public record DimFuncWithView (Func Fn, View? View) : Dim +{ + /// + /// Gets the function that computes the dimension. + /// + public Func Fn { get; } = Fn; + + /// + /// Gets the passed view that the dimension is based on. + /// + public View View { get; } = View ?? throw new ArgumentNullException (nameof (View), @"View cannot be null"); + + /// + public override string ToString () { return $"DimFuncWithView({Fn (View)})"; } + + internal override int GetAnchor (int size) + { + View?.Layout (); + + return Fn (View!); + } +} \ No newline at end of file diff --git a/Terminal.Gui/ViewBase/View.cs b/Terminal.Gui/ViewBase/View.cs index 2061ef4828..75b9d9d4bb 100644 --- a/Terminal.Gui/ViewBase/View.cs +++ b/Terminal.Gui/ViewBase/View.cs @@ -245,12 +245,6 @@ public virtual void EndInit () } } - if (ApplicationImpl.Instance.IsLegacy) - { - // TODO: Figure out how to move this out of here and just depend on LayoutNeeded in Mainloop - Layout (); // the EventLog in AllViewsTester fails to layout correctly if this is not here (convoluted Dim.Fill(Func)). - } - SetNeedsLayout (); Initialized?.Invoke (this, EventArgs.Empty); diff --git a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs index 6d45ac9b75..2c9dc6d666 100644 --- a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs +++ b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs @@ -152,6 +152,7 @@ public void ShadowStyle_Button1Pressed_Causes_Movement (ShadowStyle style, int e superView.Add (view); superView.BeginInit (); superView.EndInit (); + superView.Layout (); Thickness origThickness = view.Margin!.Thickness; view.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (0, 0) }); diff --git a/Tests/UnitTests/View/Draw/ClearViewportTests.cs b/Tests/UnitTests/View/Draw/ClearViewportTests.cs index 5aa69954fe..0b46dbd0b3 100644 --- a/Tests/UnitTests/View/Draw/ClearViewportTests.cs +++ b/Tests/UnitTests/View/Draw/ClearViewportTests.cs @@ -113,8 +113,7 @@ public void Clear_ClearsEntireViewport () superView.Add (view); superView.BeginInit (); superView.EndInit (); - superView.LayoutSubViews (); - + superView.Layout (); superView.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -163,7 +162,7 @@ public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly () superView.Add (view); superView.BeginInit (); superView.EndInit (); - superView.LayoutSubViews (); + superView.Layout (); superView.Draw (); diff --git a/Tests/UnitTests/View/Draw/ClipTests.cs b/Tests/UnitTests/View/Draw/ClipTests.cs index 893900e59b..744002666b 100644 --- a/Tests/UnitTests/View/Draw/ClipTests.cs +++ b/Tests/UnitTests/View/Draw/ClipTests.cs @@ -79,7 +79,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) superView.Add (view); superView.BeginInit (); superView.EndInit (); - superView.LayoutSubViews (); + superView.Layout (); superView.Draw (); @@ -258,6 +258,7 @@ public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped () view.Border!.Thickness = new (1); view.BeginInit (); view.EndInit (); + view.Layout (); Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ()); // Act @@ -291,6 +292,7 @@ public void SetClip_Default_ClipsToViewport () view.Border!.Thickness = new (1); view.BeginInit (); view.EndInit (); + view.Layout (); Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ()); view.Viewport = view.Viewport with { X = 1, Y = 1 }; diff --git a/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs b/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs index 03e6ad28f0..cae736429c 100644 --- a/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs +++ b/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs @@ -327,6 +327,7 @@ public void Returns_Correct_If_Start_Has_Adornment_WithSubView (int testX, int t Application.Top.Padding.Add (subview); Application.Top.BeginInit (); Application.Top.EndInit (); + Application.Top.LayoutSubViews (); View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); @@ -489,6 +490,7 @@ public void Returns_Correct_If_SubView_Has_Adornment_WithSubView (int testX, int Application.Top.Add (subview); Application.Top.BeginInit (); Application.Top.EndInit (); + Application.Top.LayoutSubViews (); View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); @@ -540,6 +542,7 @@ public void Returns_Correct_If_SubView_Is_Scrolled_And_Has_Adornment_WithSubView Application.Top.Add (subview); Application.Top.BeginInit (); Application.Top.EndInit (); + Application.Top.LayoutSubViews (); View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); diff --git a/Tests/UnitTests/View/Mouse/MouseTests.cs b/Tests/UnitTests/View/Mouse/MouseTests.cs index 6d8a68eab3..0718cb4f66 100644 --- a/Tests/UnitTests/View/Mouse/MouseTests.cs +++ b/Tests/UnitTests/View/Mouse/MouseTests.cs @@ -191,6 +191,7 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_B // When mouse is held down me.Flags = pressed; + view.Layout (); view.NewMouseEvent (me); Assert.Equal (0, clickedCount); me.Handled = false; diff --git a/Tests/UnitTests/View/TextTests.cs b/Tests/UnitTests/View/TextTests.cs index 4a75be09a0..4c342f4ee1 100644 --- a/Tests/UnitTests/View/TextTests.cs +++ b/Tests/UnitTests/View/TextTests.cs @@ -1024,6 +1024,7 @@ public void Narrow_Wide_Runes () top.Add (frame); top.BeginInit (); top.EndInit (); + top.Layout (); Assert.Equal (new (0, 0, 20, 1), horizontalView.Frame); Assert.Equal (new (0, 3, 1, 20), verticalView.Frame); @@ -1127,6 +1128,7 @@ public void SetText_RendersCorrectly () view = new Label { Text = text }; view.BeginInit (); view.EndInit (); + view.Layout (); view.Draw (); DriverAssert.AssertDriverContentsWithFrameAre (text, output); diff --git a/Tests/UnitTests/View/ViewTests.cs b/Tests/UnitTests/View/ViewTests.cs index 67c52e133c..0493476fd7 100644 --- a/Tests/UnitTests/View/ViewTests.cs +++ b/Tests/UnitTests/View/ViewTests.cs @@ -291,6 +291,7 @@ public void New_Initializes () r.BeginInit (); r.EndInit (); + r.Layout (); Assert.False (r.CanFocus); Assert.False (r.HasFocus); Assert.Equal (new (0, 0, 1, 13), r.Viewport); diff --git a/Tests/UnitTests/Views/ButtonTests.cs b/Tests/UnitTests/Views/ButtonTests.cs index 2e84f96ab7..7c9f9aed9c 100644 --- a/Tests/UnitTests/Views/ButtonTests.cs +++ b/Tests/UnitTests/Views/ButtonTests.cs @@ -232,6 +232,7 @@ public void Constructors_Defaults () Assert.True (btn.CanFocus); Application.Driver?.ClearContents (); + btn.Layout (); btn.Draw (); expected = @$" diff --git a/Tests/UnitTests/Views/ColorPicker16Tests.cs b/Tests/UnitTests/Views/ColorPicker16Tests.cs index 53182a950b..23e2af7257 100644 --- a/Tests/UnitTests/Views/ColorPicker16Tests.cs +++ b/Tests/UnitTests/Views/ColorPicker16Tests.cs @@ -14,7 +14,7 @@ public void Constructors () colorPicker.BeginInit (); colorPicker.EndInit (); - colorPicker.LayoutSubViews (); + colorPicker.Layout (); Assert.Equal (new (0, 0, 32, 4), colorPicker.Frame); } diff --git a/Tests/UnitTests/Views/ColorPickerTests.cs b/Tests/UnitTests/Views/ColorPickerTests.cs index 22144245de..f6f88db9b3 100644 --- a/Tests/UnitTests/Views/ColorPickerTests.cs +++ b/Tests/UnitTests/Views/ColorPickerTests.cs @@ -675,7 +675,7 @@ public void ColorPicker_SwitchingColorModels_ResetsBars () // Switch to HSV cp.Style.ColorModel = ColorModel.HSV; cp.ApplyStyleChanges (); - + cp.Layout (); cp.Draw (); ColorBar h = GetColorBar (cp, ColorPickerPart.Bar1); diff --git a/Tests/UnitTests/Views/LabelTests.cs b/Tests/UnitTests/Views/LabelTests.cs index 374750f2bc..57c8a214d5 100644 --- a/Tests/UnitTests/Views/LabelTests.cs +++ b/Tests/UnitTests/Views/LabelTests.cs @@ -1053,7 +1053,7 @@ public void Label_Height_Zero_Stays_Zero () win.Add (label); win.BeginInit (); win.EndInit (); - win.LayoutSubViews (); + win.Layout (); win.Draw (); Assert.Equal (5, text.Length); diff --git a/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs b/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs index 087823380e..f3d31e6dd8 100644 --- a/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs +++ b/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs @@ -2130,6 +2130,7 @@ public void MenuBar_Position_And_Size_With_HotKeys_Is_The_Same_As_Without_HotKey Assert.True (menu.NewKeyDownEvent (menu.Key)); Assert.True (menu.IsMenuOpen); View.SetClipToScreen (); + top.Layout (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (0), output); diff --git a/Tests/UnitTests/Views/ProgressBarTests.cs b/Tests/UnitTests/Views/ProgressBarTests.cs index 744ce198f9..f594086531 100644 --- a/Tests/UnitTests/Views/ProgressBarTests.cs +++ b/Tests/UnitTests/Views/ProgressBarTests.cs @@ -12,7 +12,7 @@ public void Default_Constructor () var pb = new ProgressBar (); pb.BeginInit (); pb.EndInit (); - + pb.Layout (); Assert.False (pb.CanFocus); Assert.Equal (0, pb.Fraction); @@ -32,7 +32,7 @@ public void Fraction_Redraw () pb.BeginInit (); pb.EndInit (); - pb.LayoutSubViews (); + pb.Layout (); for (var i = 0; i <= pb.Frame.Width; i++) { @@ -174,7 +174,7 @@ public void Pulse_Redraw_BidirectionalMarquee_False () pb.BeginInit (); pb.EndInit (); - pb.LayoutSubViews (); + pb.Layout (); for (var i = 0; i < 38; i++) { @@ -879,7 +879,7 @@ public void Pulse_Redraw_BidirectionalMarquee_True_Default () pb.BeginInit (); pb.EndInit (); - pb.LayoutSubViews (); + pb.Layout (); for (var i = 0; i < 38; i++) { diff --git a/Tests/UnitTests/Views/SliderTests.cs b/Tests/UnitTests/Views/SliderTests.cs index 69c56e593e..afe3f1dc15 100644 --- a/Tests/UnitTests/Views/SliderTests.cs +++ b/Tests/UnitTests/Views/SliderTests.cs @@ -514,6 +514,7 @@ private void DimAuto_Both_Respects_SuperView_ContentSize () view.Add (slider); view.BeginInit (); view.EndInit (); + view.Layout (); Size expectedSize = slider.Frame.Size; @@ -547,6 +548,7 @@ private void DimAuto_Width_Respects_SuperView_ContentSize () view.Add (slider); view.BeginInit (); view.EndInit (); + view.Layout (); Size expectedSize = slider.Frame.Size; @@ -580,6 +582,7 @@ private void DimAuto_Height_Respects_SuperView_ContentSize () view.Add (slider); view.BeginInit (); view.EndInit (); + view.Layout (); Size expectedSize = slider.Frame.Size; diff --git a/Tests/UnitTests/Views/TextFieldTests.cs b/Tests/UnitTests/Views/TextFieldTests.cs index 822871a55a..a42dff91e9 100644 --- a/Tests/UnitTests/Views/TextFieldTests.cs +++ b/Tests/UnitTests/Views/TextFieldTests.cs @@ -1673,6 +1673,7 @@ public void Draw_Esc_Rune () var tf = new TextField { Width = 5, Text = "\u001b" }; tf.BeginInit (); tf.EndInit (); + tf.Layout (); tf.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ("\u241b", output); diff --git a/Tests/UnitTests/Views/TileViewTests.cs b/Tests/UnitTests/Views/TileViewTests.cs index 8981738e71..f46051b6b5 100644 --- a/Tests/UnitTests/Views/TileViewTests.cs +++ b/Tests/UnitTests/Views/TileViewTests.cs @@ -819,6 +819,10 @@ public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringRebuildForTileCount Assert.Equal (1, myReusableView.DisposalCount); } ); + + Assert.NotNull (Application.Top); + Application.Top.Dispose (); + Application.Shutdown (); } [Theory] @@ -848,6 +852,10 @@ public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringRemoveTile (int idx) Assert.True (myReusableView.DisposalCount >= 1); } ); + + Assert.NotNull (Application.Top); + Application.Top.Dispose (); + Application.Shutdown (); } [Fact] diff --git a/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs b/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs index fd854b1e48..7bb7bf950a 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs @@ -263,6 +263,7 @@ public void With_SubView_Using_PosCenter_Combine (int minWidth, int maxWidth, in view.BeginInit (); view.EndInit (); + view.Layout (); // subview should be centered in the parent view + 1 Assert.Equal ((view.Viewport.Width - subview.Frame.Width) / 2 + 1, subview.Frame.X); @@ -338,6 +339,7 @@ public void With_SubView_Using_PosAnchorEnd (int minWidth, int maxWidth, int min view.BeginInit (); view.EndInit (); + view.Layout (); // subview should be at the end of the view Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); @@ -392,6 +394,7 @@ public void With_SubView_And_SubView_Using_PosAnchorEnd (int minWidth, int maxWi view.BeginInit (); view.EndInit (); + view.Layout (); // subview should be at the end of the view Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); @@ -446,6 +449,7 @@ public void With_DimAutoSubView_And_SubView_Using_PosAnchorEnd (int minWidth, in view.BeginInit (); view.EndInit (); + view.Layout (); // subview should be at the end of the view Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); @@ -508,6 +512,7 @@ public void With_PosViewSubView_And_SubView_Using_PosAnchorEnd (int minWidth, in view.BeginInit (); view.EndInit (); + view.Layout (); // subview should be at the end of the view Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); @@ -572,11 +577,13 @@ public void With_DimViewSubView_And_SubView_Using_PosAnchorEnd (int minWidth, in view.BeginInit (); view.EndInit (); + view.Layout (); // subview should be at the end of the view Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); Assert.Equal (view.Viewport.Height - subview.Frame.Height, subview.Frame.Y); } + [Theory] [InlineData (0, 10, 0, 10, 10, 2)] [InlineData (0, 5, 0, 5, 5, 3)] // max width of 5 should cause wordwrap at 5 giving a height of 2 + 1 diff --git a/Tests/UnitTestsParallelizable/View/Layout/Dim.FuncWithViewTests.cs b/Tests/UnitTestsParallelizable/View/Layout/Dim.FuncWithViewTests.cs new file mode 100644 index 0000000000..75e3a1bf95 --- /dev/null +++ b/Tests/UnitTestsParallelizable/View/Layout/Dim.FuncWithViewTests.cs @@ -0,0 +1,58 @@ +using Xunit.Abstractions; +using static Terminal.Gui.ViewBase.Dim; + +namespace Terminal.Gui.LayoutTests; + +public class DimFuncWithViewTests (ITestOutputHelper output) +{ + private readonly ITestOutputHelper _output = output; + + [Fact] + public void DimFuncWithView_Equal () + { + Func f1 = v => v.Frame.Width; + Func f2 = v => v.Frame.Width; + View view1 = new (); + View view2 = new (); + + Dim dim1 = FuncWithView (f1, view1); + Dim dim2 = FuncWithView (f1, view1); + Assert.Equal (dim1, dim2); + + dim2 = FuncWithView (f2, view2); + Assert.NotEqual (dim1, dim2); + + view2.Width = 1; + Assert.NotEqual (dim1, dim2); + Assert.Equal (1, f2 (view2)); + } + + [Fact] + public void DimFuncWithView_SetsValue () + { + View view = new () { Text = "Test" }; + Dim dim = FuncWithView (v => v.Text.Length, view); + Assert.Equal ("DimFuncWithView(4)", dim.ToString ()); + + view.Text = "New Test"; + Assert.Equal ("DimFuncWithView(8)", dim.ToString ()); + + view.Text = ""; + Assert.Equal ("DimFuncWithView(0)", dim.ToString ()); + } + + [Fact] + public void DimFuncWithView_Calculate_ReturnsCorrectValue () + { + View view = new () { Width = 10 }; + var dim = new DimFuncWithView (v => v.Frame.Width, view); + int result = dim.Calculate (0, 100, view, Dimension.None); + Assert.Equal (10, result); + } + + [Fact] + public void DimFuncWithView_Throws_ArgumentNullException_If_View_Is_Null () + { + Assert.Throws (() => FuncWithView (v => 0, null)); + } +} diff --git a/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs b/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs index 2aa4e65149..f8a5607874 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs @@ -376,24 +376,24 @@ public void LayoutSubViews_LayoutStarted_Complete () Assert.Equal (0, layoutStartedCount); Assert.Equal (0, layoutCompleteCount); - superView.EndInit (); - Assert.Equal (1, borderLayoutStartedCount); - Assert.Equal (1, borderLayoutCompleteCount); - Assert.Equal (2, layoutStartedCount); - Assert.Equal (2, layoutCompleteCount); + superView.EndInit (); // Only sets NeedsLayout + Assert.Equal (0, borderLayoutStartedCount); + Assert.Equal (0, borderLayoutCompleteCount); + Assert.Equal (0, layoutStartedCount); + Assert.Equal (0, layoutCompleteCount); superView.LayoutSubViews (); - Assert.Equal (1, borderLayoutStartedCount); - Assert.Equal (1, borderLayoutCompleteCount); - Assert.Equal (3, layoutStartedCount); - Assert.Equal (3, layoutCompleteCount); + Assert.Equal (0, borderLayoutStartedCount); // No Border + Assert.Equal (0, borderLayoutCompleteCount); // No Border + Assert.Equal (1, layoutStartedCount); + Assert.Equal (1, layoutCompleteCount); superView.SetNeedsLayout (); superView.LayoutSubViews (); - Assert.Equal (1, borderLayoutStartedCount); - Assert.Equal (1, borderLayoutCompleteCount); - Assert.Equal (4, layoutStartedCount); - Assert.Equal (4, layoutCompleteCount); + Assert.Equal (0, borderLayoutStartedCount); // No Border + Assert.Equal (0, borderLayoutCompleteCount); // No Border + Assert.Equal (2, layoutStartedCount); + Assert.Equal (2, layoutCompleteCount); superView.Dispose (); } @@ -443,8 +443,8 @@ public void LayoutSubViews_Raises_LayoutStarted_LayoutComplete () superView.BeginInit (); superView.EndInit (); superView.LayoutSubViews (); - Assert.Equal (3, layoutStartedRaised); - Assert.Equal (3, layoutCompleteRaised); + Assert.Equal (2, layoutStartedRaised); + Assert.Equal (2, layoutCompleteRaised); } [Fact] diff --git a/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs b/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs index 133ac3ab37..38be25a021 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs @@ -409,6 +409,7 @@ public void PosDimFunc () // BUGBUG: IsInitialized need to be true before calculate view.BeginInit (); view.EndInit (); + view.Layout (); view.SetRelativeLayout (screen); Assert.Equal (27, view.Frame.X); Assert.Equal (0, view.Frame.Y); diff --git a/Tests/UnitTestsParallelizable/View/TextTests.cs b/Tests/UnitTestsParallelizable/View/TextTests.cs index 31ca7eea84..f86fdcc39e 100644 --- a/Tests/UnitTestsParallelizable/View/TextTests.cs +++ b/Tests/UnitTestsParallelizable/View/TextTests.cs @@ -170,6 +170,7 @@ public void TextDirection_Horizontal_Dims_Correct_WidthAbsolute () }; view.BeginInit (); view.EndInit (); + view.Layout (); Assert.Equal (new (0, 0, 10, 1), view.Frame); Assert.Equal (new (0, 0, 10, 1), view.Viewport); diff --git a/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs b/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs index a38f6188ba..664479259d 100644 --- a/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs +++ b/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs @@ -543,6 +543,7 @@ public void PositionCursor_Respect_GetColumns () var tf = new TextField { Width = 5 }; tf.BeginInit (); tf.EndInit (); + tf.Layout (); tf.NewKeyDownEvent (new ("📄")); Assert.Equal (1, tf.CursorPosition); From 50015ac6da3a004fa2b7f77d545518f17cc45a4f Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 25 Jul 2025 11:39:08 +0100 Subject: [PATCH 03/34] Revert to iteration which will handle the necessary processes --- Terminal.Gui/App/Application.Run.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index eafe91f141..8758e44b31 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -212,7 +212,8 @@ public static RunState Begin (Toplevel toplevel) NotifyNewRunState?.Invoke (toplevel, new (rs)); - LayoutAndDraw (true); + // Force an Idle event so that an Iteration (and Refresh) happen. + Invoke (() => { }); return rs; } From dadc75e897d7301c248c30e91d2c27ca3020db78 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 25 Jul 2025 12:33:59 +0100 Subject: [PATCH 04/34] Revert "Revert to iteration which will handle the necessary processes" This reverts commit 50015ac6da3a004fa2b7f77d545518f17cc45a4f. --- Terminal.Gui/App/Application.Run.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index 8758e44b31..eafe91f141 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -212,8 +212,7 @@ public static RunState Begin (Toplevel toplevel) NotifyNewRunState?.Invoke (toplevel, new (rs)); - // Force an Idle event so that an Iteration (and Refresh) happen. - Invoke (() => { }); + LayoutAndDraw (true); return rs; } From 4283f010c27c26bbc41c988fe41dfff34bbedd59 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 25 Jul 2025 12:44:03 +0100 Subject: [PATCH 05/34] Layout and draw before position cursor --- Terminal.Gui/App/Application.Run.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index eafe91f141..11f9b3bbec 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -205,6 +205,8 @@ public static RunState Begin (Toplevel toplevel) toplevel.OnLoaded (); + LayoutAndDraw (true); + if (PositionCursor ()) { Driver?.UpdateCursor (); @@ -212,8 +214,6 @@ public static RunState Begin (Toplevel toplevel) NotifyNewRunState?.Invoke (toplevel, new (rs)); - LayoutAndDraw (true); - return rs; } From 8f51903e75ee686679fe161faad4d5b557bb09f0 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 25 Jul 2025 18:15:49 +0100 Subject: [PATCH 06/34] Add optional View parameter and property to the DimFunc and PosFunc --- Examples/UICatalog/Scenarios/Adornments.cs | 2 +- .../UICatalog/Scenarios/AllViewsTester.cs | 8 +-- Examples/UICatalog/Scenarios/Arrangement.cs | 2 +- .../Scenarios/CharacterMap/CharacterMap.cs | 2 +- .../Scenarios/ConfigurationEditor.cs | 2 +- .../UICatalog/Scenarios/DynamicMenuBar.cs | 2 +- .../EditorsAndHelpers/AdornmentEditor.cs | 2 +- .../EditorsAndHelpers/AdornmentsEditor.cs | 2 +- .../Scenarios/EditorsAndHelpers/DimEditor.cs | 6 +- .../Scenarios/EditorsAndHelpers/EventLog.cs | 2 +- .../Scenarios/EditorsAndHelpers/PosEditor.cs | 6 +- Examples/UICatalog/Scenarios/ListColumns.cs | 2 +- .../Scenarios/ListViewWithSelection.cs | 2 +- Examples/UICatalog/Scenarios/Mouse.cs | 2 +- Examples/UICatalog/Scenarios/ScrollBarDemo.cs | 16 ++--- Examples/UICatalog/Scenarios/Shortcuts.cs | 14 ++--- .../Scenarios/SingleBackgroundWorker.cs | 2 +- .../UICatalog/Scenarios/ViewportSettings.cs | 6 +- Examples/UICatalog/Scenarios/Wizards.cs | 10 ++-- Examples/UICatalog/UICatalogTop.cs | 8 +-- Terminal.Gui/ViewBase/Layout/Dim.cs | 10 +--- Terminal.Gui/ViewBase/Layout/DimFunc.cs | 24 ++++++-- .../ViewBase/Layout/DimFuncWithView.cs | 35 ----------- Terminal.Gui/ViewBase/Layout/Pos.cs | 8 ++- Terminal.Gui/ViewBase/Layout/PosFunc.cs | 28 +++++++-- Terminal.Gui/ViewBase/View.ScrollBars.cs | 4 +- Terminal.Gui/Views/FileDialogs/FileDialog.cs | 4 +- Terminal.Gui/Views/Menu/Menuv2.cs | 4 +- Terminal.Gui/Views/MessageBox.cs | 8 +-- Terminal.Gui/Views/NumericUpDown.cs | 2 +- Terminal.Gui/Views/ScrollBar/ScrollBar.cs | 4 +- Terminal.Gui/Views/Shortcut.cs | 8 +-- Terminal.Gui/Views/Wizard/Wizard.cs | 8 +-- Tests/UnitTests/Views/LabelTests.cs | 2 +- Tests/UnitTests/Views/TileViewTests.cs | 2 +- .../View/Layout/Dim.AutoTests.DimTypes.cs | 2 +- .../View/Layout/Dim.AutoTests.PosTypes.cs | 2 +- .../View/Layout/Dim.FillTests.cs | 4 +- .../View/Layout/Dim.FuncTests.cs | 53 +++++++++++++++-- .../View/Layout/Dim.FuncWithViewTests.cs | 58 ------------------- .../View/Layout/FrameTests.cs | 16 ++--- .../View/Layout/Pos.FuncTests.cs | 54 +++++++++++++++-- .../View/Layout/Pos.Tests.cs | 4 +- .../View/Layout/SetLayoutTests.cs | 8 +-- .../View/Layout/SetRelativeLayoutTests.cs | 5 +- 45 files changed, 238 insertions(+), 217 deletions(-) delete mode 100644 Terminal.Gui/ViewBase/Layout/DimFuncWithView.cs delete mode 100644 Tests/UnitTestsParallelizable/View/Layout/Dim.FuncWithViewTests.cs diff --git a/Examples/UICatalog/Scenarios/Adornments.cs b/Examples/UICatalog/Scenarios/Adornments.cs index aff70c09c2..5e0efc6287 100644 --- a/Examples/UICatalog/Scenarios/Adornments.cs +++ b/Examples/UICatalog/Scenarios/Adornments.cs @@ -35,7 +35,7 @@ public override void Main () Title = "The _Window", Arrangement = ViewArrangement.Overlapped | ViewArrangement.Movable, - Width = Dim.Fill (Dim.Func (() => editor.Frame.Width)), + Width = Dim.Fill (Dim.Func (_ => editor.Frame.Width)), Height = Dim.Fill () }; app.Add (window); diff --git a/Examples/UICatalog/Scenarios/AllViewsTester.cs b/Examples/UICatalog/Scenarios/AllViewsTester.cs index 71d9edbe56..f967742bcd 100644 --- a/Examples/UICatalog/Scenarios/AllViewsTester.cs +++ b/Examples/UICatalog/Scenarios/AllViewsTester.cs @@ -101,7 +101,7 @@ public override void Main () { Title = "Arrangement [_3]", X = Pos.Right (_classListView) - 1, - Y = Pos.Bottom (_adornmentsEditor) - Pos.Func (() => _adornmentsEditor.Frame.Height == 1 ? 0 : 1), + Y = Pos.Bottom (_adornmentsEditor) - Pos.Func (_ => _adornmentsEditor.Frame.Height == 1 ? 0 : 1), Width = Dim.Width (_adornmentsEditor), Height = Dim.Fill (), AutoSelectViewToEdit = false, @@ -134,7 +134,7 @@ public override void Main () { Title = "ViewportSettings [_5]", X = Pos.Right (_arrangementEditor) - 1, - Y = Pos.Bottom (_layoutEditor) - Pos.Func (() => _layoutEditor.Frame.Height == 1 ? 0 : 1), + Y = Pos.Bottom (_layoutEditor) - Pos.Func (_ => _layoutEditor.Frame.Height == 1 ? 0 : 1), Width = Dim.Width (_layoutEditor), Height = Dim.Auto (), CanFocus = true, @@ -148,7 +148,7 @@ public override void Main () { Title = "View Properties [_6]", X = Pos.Right (_adornmentsEditor) - 1, - Y = Pos.Bottom (_viewportSettingsEditor) - Pos.Func (() => _viewportSettingsEditor.Frame.Height == 1 ? 0 : 1), + Y = Pos.Bottom (_viewportSettingsEditor) - Pos.Func (_ => _viewportSettingsEditor.Frame.Height == 1 ? 0 : 1), Width = Dim.Width (_layoutEditor), Height = Dim.Auto (), CanFocus = true, @@ -171,7 +171,7 @@ public override void Main () _layoutEditor.Width = Dim.Fill ( Dim.Func ( - () => + _ => { if (_eventLog.NeedsLayout) { diff --git a/Examples/UICatalog/Scenarios/Arrangement.cs b/Examples/UICatalog/Scenarios/Arrangement.cs index 6aae631c86..867e36f1da 100644 --- a/Examples/UICatalog/Scenarios/Arrangement.cs +++ b/Examples/UICatalog/Scenarios/Arrangement.cs @@ -66,7 +66,7 @@ public override void Main () View tiledView3 = CreateTiledView (2, Pos.Right (tiledView2) - 1, Pos.Top (tiledView2)); tiledView3.Height = Dim.Height (tiledView1); View tiledView4 = CreateTiledView (3, Pos.Left (tiledView1), Pos.Bottom (tiledView1) - 1); - tiledView4.Width = Dim.Func (() => tiledView3.Frame.Width + tiledView2.Frame.Width + tiledView1.Frame.Width - 2); + tiledView4.Width = Dim.Func (_ => tiledView3.Frame.Width + tiledView2.Frame.Width + tiledView1.Frame.Width - 2); View movableSizeableWithProgress = CreateOverlappedView (2, 10, 8); movableSizeableWithProgress.Title = "Movable _& Sizable"; diff --git a/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs b/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs index 1eb68f1d1d..5a7872c334 100644 --- a/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs +++ b/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs @@ -171,7 +171,7 @@ public override void Main () }; top.Add (menu); - _charMap.Width = Dim.Fill (Dim.FuncWithView (v => v!.Frame.Width, _categoryList)); + _charMap.Width = Dim.Fill (Dim.Func (v => v!.Frame.Width, _categoryList)); _charMap.SelectedCodePoint = 0; _charMap.SetFocus (); diff --git a/Examples/UICatalog/Scenarios/ConfigurationEditor.cs b/Examples/UICatalog/Scenarios/ConfigurationEditor.cs index cd4a8563aa..a5beca9e98 100644 --- a/Examples/UICatalog/Scenarios/ConfigurationEditor.cs +++ b/Examples/UICatalog/Scenarios/ConfigurationEditor.cs @@ -55,7 +55,7 @@ public override void Main () _tabView = new () { Width = Dim.Fill (), - Height = Dim.Fill (Dim.Func (() => statusBar.Frame.Height)) + Height = Dim.Fill (Dim.Func (_ => statusBar.Frame.Height)) }; win.Add (_tabView, statusBar); diff --git a/Examples/UICatalog/Scenarios/DynamicMenuBar.cs b/Examples/UICatalog/Scenarios/DynamicMenuBar.cs index fc61a7a3b6..b800cc3ef1 100644 --- a/Examples/UICatalog/Scenarios/DynamicMenuBar.cs +++ b/Examples/UICatalog/Scenarios/DynamicMenuBar.cs @@ -598,7 +598,7 @@ public DynamicMenuBarSample () X = Pos.Right (btnPrevious) + 1, Y = Pos.Top (btnPrevious), - Width = Dim.Fill () - Dim.Func (() => btnAdd.Frame.Width + 1), + Width = Dim.Fill () - Dim.Func (_ => btnAdd.Frame.Width + 1), Height = 1 }; diff --git a/Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentEditor.cs b/Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentEditor.cs index d1243b424b..907cbb42e3 100644 --- a/Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentEditor.cs +++ b/Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentEditor.cs @@ -100,7 +100,7 @@ private void AdornmentEditor_Initialized (object? sender, EventArgs e) _leftEdit = new () { - X = Pos.Left (_topEdit) - Pos.Func (() => _topEdit.Text.Length) - 2, Y = Pos.Bottom (_topEdit), + X = Pos.Left (_topEdit) - Pos.Func (_ => _topEdit.Text.Length) - 2, Y = Pos.Bottom (_topEdit), Format = _topEdit.Format }; diff --git a/Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentsEditor.cs b/Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentsEditor.cs index 382865f4b1..74d7acee88 100644 --- a/Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentsEditor.cs +++ b/Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentsEditor.cs @@ -122,7 +122,7 @@ private void AdornmentsEditor_Initialized (object? sender, EventArgs e) PaddingEditor.Border!.Thickness = PaddingEditor.Border.Thickness with { Bottom = 0 }; Add (PaddingEditor); - Width = Dim.Auto (maximumContentDim: Dim.Func (() => MarginEditor.Frame.Width - 2)); + Width = Dim.Auto (maximumContentDim: Dim.Func (_ => MarginEditor.Frame.Width - 2)); MarginEditor.ExpanderButton!.Collapsed = true; BorderEditor.ExpanderButton!.Collapsed = true; diff --git a/Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs b/Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs index 523c4f1dab..37007997d3 100644 --- a/Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs +++ b/Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs @@ -68,7 +68,7 @@ protected override void OnUpdateLayoutSettings () break; case DimFunc func: _valueEdit.Enabled = true; - _value = func.Fn (); + _value = func.Fn (null); _valueEdit!.Text = _value.ToString (); break; case DimPercent percent: @@ -98,7 +98,7 @@ private void DimEditor_Initialized (object? sender, EventArgs e) { X = Pos.Right (label) + 1, Y = 0, - Width = Dim.Func (() => _radioItems.Max (i => i.GetColumns ()) - label.Frame.Width + 1), + Width = Dim.Func (_ => _radioItems.Max (i => i.GetColumns ()) - label.Frame.Width + 1), Text = $"{_value}" }; @@ -141,7 +141,7 @@ private void DimChanged () 0 => Dim.Absolute (_value), 1 => Dim.Auto (), 2 => Dim.Fill (_value), - 3 => Dim.Func (() => _value), + 3 => Dim.Func (_ => _value), 4 => Dim.Percent (_value), _ => Dimension == Dimension.Width ? ViewToEdit.Width : ViewToEdit.Height }; diff --git a/Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs b/Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs index b64acc865e..9b59d88126 100644 --- a/Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs +++ b/Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs @@ -20,7 +20,7 @@ public EventLog () Y = 0; Width = Dim.Func ( - () => + _ => { if (!IsInitialized) { diff --git a/Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs b/Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs index c67a303d1a..012b2409ef 100644 --- a/Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs +++ b/Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs @@ -70,7 +70,7 @@ protected override void OnUpdateLayoutSettings () break; case PosFunc func: _valueEdit.Enabled = true; - _value = func.Fn (); + _value = func.Fn (null); _valueEdit!.Text = _value.ToString (); break; @@ -98,7 +98,7 @@ private void PosEditor_Initialized (object? sender, EventArgs e) { X = Pos.Right (label) + 1, Y = 0, - Width = Dim.Func (() => _radioItems.Max (i => i.GetColumns ()) - label.Frame.Width + 1), + Width = Dim.Func (_ => _radioItems.Max (i => i.GetColumns ()) - label.Frame.Width + 1), Text = $"{_value}" }; @@ -142,7 +142,7 @@ private void PosChanged () 1 => Pos.Align (Alignment.Start), 2 => new PosAnchorEnd (), 3 => Pos.Center (), - 4 => Pos.Func (() => _value), + 4 => Pos.Func (_ => _value), 5 => Pos.Percent (_value), _ => Dimension == Dimension.Width ? ViewToEdit.X : ViewToEdit.Y }; diff --git a/Examples/UICatalog/Scenarios/ListColumns.cs b/Examples/UICatalog/Scenarios/ListColumns.cs index 402e2944db..5861acaada 100644 --- a/Examples/UICatalog/Scenarios/ListColumns.cs +++ b/Examples/UICatalog/Scenarios/ListColumns.cs @@ -252,7 +252,7 @@ public override void Main () top.Add (menu, appWindow, statusBar); appWindow.Y = 1; - appWindow.Height = Dim.Fill(Dim.Func (() => statusBar.Frame.Height)); + appWindow.Height = Dim.Fill(Dim.Func (_ => statusBar.Frame.Height)); // Run - Start the application. Application.Run (top); diff --git a/Examples/UICatalog/Scenarios/ListViewWithSelection.cs b/Examples/UICatalog/Scenarios/ListViewWithSelection.cs index 123e4d54a5..874d061a58 100644 --- a/Examples/UICatalog/Scenarios/ListViewWithSelection.cs +++ b/Examples/UICatalog/Scenarios/ListViewWithSelection.cs @@ -76,7 +76,7 @@ public override void Main () Title = "_ListView", X = 0, Y = Pos.Bottom (_allowMarkingCb), - Width = Dim.Func (() => _listView?.MaxLength ?? 10), + Width = Dim.Func (_ => _listView?.MaxLength ?? 10), Height = Dim.Fill (), AllowsMarking = false, AllowsMultipleSelection = false, diff --git a/Examples/UICatalog/Scenarios/Mouse.cs b/Examples/UICatalog/Scenarios/Mouse.cs index 658789cf6e..cb047614cd 100644 --- a/Examples/UICatalog/Scenarios/Mouse.cs +++ b/Examples/UICatalog/Scenarios/Mouse.cs @@ -109,7 +109,7 @@ void DemoPaddingOnInitialized (object o, EventArgs eventArgs) X = 0, Y = 0, Width = Dim.Fill (), - Height = Dim.Func (() => demo.Padding.Thickness.Top), + Height = Dim.Func (_ => demo.Padding.Thickness.Top), Title = "inPadding", Id = "inPadding" }); diff --git a/Examples/UICatalog/Scenarios/ScrollBarDemo.cs b/Examples/UICatalog/Scenarios/ScrollBarDemo.cs index 7376a7b54d..0a6f0bc705 100644 --- a/Examples/UICatalog/Scenarios/ScrollBarDemo.cs +++ b/Examples/UICatalog/Scenarios/ScrollBarDemo.cs @@ -72,7 +72,7 @@ int GetMaxLabelWidth (int groupId) Text = "_Width/Height:", TextAlignment = Alignment.End, Y = Pos.Align (Alignment.Start, AlignmentModes.StartToEnd, groupId: 1), - Width = Dim.Func (() => GetMaxLabelWidth (1)) + Width = Dim.Func (_ => GetMaxLabelWidth (1)) }; demoFrame.Add (lblWidthHeight); @@ -114,7 +114,7 @@ int GetMaxLabelWidth (int groupId) Text = "_Orientation:", TextAlignment = Alignment.End, Y = Pos.Align (Alignment.Start, groupId: 1), - Width = Dim.Func (() => GetMaxLabelWidth (1)) + Width = Dim.Func (_ => GetMaxLabelWidth (1)) }; demoFrame.Add (lblOrientationLabel); @@ -160,7 +160,7 @@ int GetMaxLabelWidth (int groupId) Text = "Scrollable_ContentSize:", TextAlignment = Alignment.End, Y = Pos.Align (Alignment.Start, groupId: 1), - Width = Dim.Func (() => GetMaxLabelWidth (1)) + Width = Dim.Func (_ => GetMaxLabelWidth (1)) }; demoFrame.Add (lblSize); @@ -193,7 +193,7 @@ int GetMaxLabelWidth (int groupId) Text = "_VisibleContentSize:", TextAlignment = Alignment.End, Y = Pos.Align (Alignment.Start, groupId: 1), - Width = Dim.Func (() => GetMaxLabelWidth (1)) + Width = Dim.Func (_ => GetMaxLabelWidth (1)) }; demoFrame.Add (lblVisibleContentSize); @@ -226,7 +226,7 @@ int GetMaxLabelWidth (int groupId) Text = "_Position:", TextAlignment = Alignment.End, Y = Pos.Align (Alignment.Start, groupId: 1), - Width = Dim.Func (() => GetMaxLabelWidth (1)) + Width = Dim.Func (_ => GetMaxLabelWidth (1)) }; demoFrame.Add (lblPosition); @@ -264,7 +264,7 @@ int GetMaxLabelWidth (int groupId) Text = "Options:", TextAlignment = Alignment.End, Y = Pos.Align (Alignment.Start, groupId: 1), - Width = Dim.Func (() => GetMaxLabelWidth (1)) + Width = Dim.Func (_ => GetMaxLabelWidth (1)) }; demoFrame.Add (lblOptions); var autoShow = new CheckBox @@ -282,7 +282,7 @@ int GetMaxLabelWidth (int groupId) Text = "SliderPosition:", TextAlignment = Alignment.End, Y = Pos.Align (Alignment.Start, groupId: 1), - Width = Dim.Func (() => GetMaxLabelWidth (1)) + Width = Dim.Func (_ => GetMaxLabelWidth (1)) }; demoFrame.Add (lblSliderPosition); @@ -299,7 +299,7 @@ int GetMaxLabelWidth (int groupId) Text = "Scrolled:", TextAlignment = Alignment.End, Y = Pos.Align (Alignment.Start, groupId: 1), - Width = Dim.Func (() => GetMaxLabelWidth (1)) + Width = Dim.Func (_ => GetMaxLabelWidth (1)) }; demoFrame.Add (lblScrolled); diff --git a/Examples/UICatalog/Scenarios/Shortcuts.cs b/Examples/UICatalog/Scenarios/Shortcuts.cs index 0ad863e94d..73ec7a5ddb 100644 --- a/Examples/UICatalog/Scenarios/Shortcuts.cs +++ b/Examples/UICatalog/Scenarios/Shortcuts.cs @@ -43,14 +43,14 @@ private void App_Loaded (object? sender, EventArgs e) }; eventLog.Width = Dim.Func ( - () => Math.Min ( - Application.Top.Viewport.Width / 2, - eventLog?.MaxLength + eventLog!.GetAdornmentsThickness ().Horizontal ?? 0)); + _ => Math.Min ( + Application.Top.Viewport.Width / 2, + eventLog?.MaxLength + eventLog!.GetAdornmentsThickness ().Horizontal ?? 0)); eventLog.Width = Dim.Func ( - () => Math.Min ( - eventLog.SuperView!.Viewport.Width / 2, - eventLog?.MaxLength + eventLog!.GetAdornmentsThickness ().Horizontal ?? 0)); + _ => Math.Min ( + eventLog.SuperView!.Viewport.Width / 2, + eventLog?.MaxLength + eventLog!.GetAdornmentsThickness ().Horizontal ?? 0)); Application.Top.Add (eventLog); var alignKeysShortcut = new Shortcut @@ -193,7 +193,7 @@ select peer.Key.ToString ().GetColumns ()).Prepend (max) Id = "appShortcut", X = 0, Y = Pos.Bottom (canFocusShortcut), - Width = Dim.Fill (Dim.Func (() => eventLog.Frame.Width)), + Width = Dim.Fill (Dim.Func (_ => eventLog.Frame.Width)), Title = "A_pp Shortcut", Key = Key.F1, Text = "Width is DimFill", diff --git a/Examples/UICatalog/Scenarios/SingleBackgroundWorker.cs b/Examples/UICatalog/Scenarios/SingleBackgroundWorker.cs index add1555ba3..62c35ac40e 100644 --- a/Examples/UICatalog/Scenarios/SingleBackgroundWorker.cs +++ b/Examples/UICatalog/Scenarios/SingleBackgroundWorker.cs @@ -83,7 +83,7 @@ public MainApp () workerLogTop.Add (_listLog); workerLogTop.Y = 1; - workerLogTop.Height = Dim.Fill (Dim.Func (() => statusBar.Frame.Height)); + workerLogTop.Height = Dim.Fill (Dim.Func (_ => statusBar.Frame.Height)); Add (menu, workerLogTop, statusBar); Title = "MainApp"; diff --git a/Examples/UICatalog/Scenarios/ViewportSettings.cs b/Examples/UICatalog/Scenarios/ViewportSettings.cs index 7e488d0c73..fa801e37c4 100644 --- a/Examples/UICatalog/Scenarios/ViewportSettings.cs +++ b/Examples/UICatalog/Scenarios/ViewportSettings.cs @@ -124,8 +124,8 @@ public override void Main () var view = new ViewportSettingsDemoView { Title = "ViewportSettings Demo View", - Width = Dim.Fill (Dim.Func (() => app.IsInitialized ? adornmentsEditor.Frame.Width + 1 : 1)), - Height = Dim.Fill (Dim.Func (() => app.IsInitialized ? viewportSettingsEditor.Frame.Height : 1)) + Width = Dim.Fill (Dim.Func (_ => app.IsInitialized ? adornmentsEditor.Frame.Width + 1 : 1)), + Height = Dim.Fill (Dim.Func (_ => app.IsInitialized ? viewportSettingsEditor.Frame.Height : 1)) }; app.Add (view); @@ -164,7 +164,7 @@ public override void Main () { X = Pos.Center (), Y = Pos.Bottom (textView) + 1, - Width = Dim.Auto (DimAutoStyle.Content, maximumContentDim: Dim.Func (() => view.GetContentSize ().Width)), + Width = Dim.Auto (DimAutoStyle.Content, maximumContentDim: Dim.Func (_ => view.GetContentSize ().Width)), Height = Dim.Auto (DimAutoStyle.Content, maximumContentDim: Dim.Percent (20)), }; diff --git a/Examples/UICatalog/Scenarios/Wizards.cs b/Examples/UICatalog/Scenarios/Wizards.cs index e74e54d9ed..f3d4bf8310 100644 --- a/Examples/UICatalog/Scenarios/Wizards.cs +++ b/Examples/UICatalog/Scenarios/Wizards.cs @@ -282,11 +282,11 @@ void Win_Loaded (object sender, EventArgs args) someText.Height = Dim.Fill ( Dim.Func ( - () => someText.SuperView is { IsInitialized: true } - ? someText.SuperView.SubViews - .First (view => view.Y.Has (out _)) - .Frame.Height - : 1)); + v => someText.SuperView is { IsInitialized: true } + ? someText.SuperView.SubViews + .First (view => view.Y.Has (out _)) + .Frame.Height + : 1)); var help = "This is helpful."; fourthStep.Add (someText); diff --git a/Examples/UICatalog/UICatalogTop.cs b/Examples/UICatalog/UICatalogTop.cs index ad87b0a12d..f5a8213f88 100644 --- a/Examples/UICatalog/UICatalogTop.cs +++ b/Examples/UICatalog/UICatalogTop.cs @@ -406,7 +406,7 @@ private TableView CreateScenarioList () Width = Dim.Fill (), Height = Dim.Fill ( Dim.Func ( - () => + _ => { if (_statusBar!.NeedsLayout) { @@ -517,7 +517,7 @@ private ListView CreateCategoryList () Width = Dim.Auto (), Height = Dim.Fill ( Dim.Func ( - () => + _ => { if (_statusBar!.NeedsLayout) { @@ -595,8 +595,8 @@ private StatusBar CreateStatusBar () // ReSharper disable All statusBar.Height = Dim.Auto ( DimAutoStyle.Auto, - minimumContentDim: Dim.Func (() => statusBar.Visible ? 1 : 0), - maximumContentDim: Dim.Func (() => statusBar.Visible ? 1 : 0)); + minimumContentDim: Dim.Func (_ => statusBar.Visible ? 1 : 0), + maximumContentDim: Dim.Func (_ => statusBar.Visible ? 1 : 0)); // ReSharper restore All _shQuit = new () diff --git a/Terminal.Gui/ViewBase/Layout/Dim.cs b/Terminal.Gui/ViewBase/Layout/Dim.cs index c674d3333b..d9bdaf3620 100644 --- a/Terminal.Gui/ViewBase/Layout/Dim.cs +++ b/Terminal.Gui/ViewBase/Layout/Dim.cs @@ -140,14 +140,6 @@ public abstract record Dim : IEqualityOperators /// Margin to use. public static Dim? Fill (Dim margin) { return new DimFill (margin); } - /// - /// Creates a function object that computes the dimension by executing the provided function. - /// The function will be called every time the dimension is needed. - /// - /// The function to be executed. - /// The returned from the function. - public static Dim Func (Func function) { return new DimFunc (function); } - /// /// Creates a function object that computes the dimension based on the passed view and by executing /// the provided function. @@ -156,7 +148,7 @@ public abstract record Dim : IEqualityOperators /// The function to be executed. /// The view where the data will be retrieved. /// The returned from the function based on the passed view. - public static Dim FuncWithView (Func function, View? view) { return new DimFuncWithView (function, view); } + public static Dim Func (Func function, View? view = null) { return new DimFunc (function, view); } /// Creates a object that tracks the Height of the specified . /// The height of the other . diff --git a/Terminal.Gui/ViewBase/Layout/DimFunc.cs b/Terminal.Gui/ViewBase/Layout/DimFunc.cs index 9047bd96b1..1ba061f926 100644 --- a/Terminal.Gui/ViewBase/Layout/DimFunc.cs +++ b/Terminal.Gui/ViewBase/Layout/DimFunc.cs @@ -2,22 +2,34 @@ namespace Terminal.Gui.ViewBase; /// -/// Represents a function object that computes the dimension by executing the provided function. +/// Represents a function object that computes the dimension based on the passed view and by +/// executing the provided function. /// /// /// 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. If this function throws ... -public record DimFunc (Func Fn) : Dim +/// The returned from the function based on the passed view. +public record DimFunc (Func Fn, View? View = null) : Dim { /// /// Gets the function that computes the dimension. /// - public Func Fn { get; } = Fn; + public Func Fn { get; } = Fn; + + /// + /// Gets the passed view that the dimension is based on. + /// + public View? View { get; } = View; /// - public override string ToString () { return $"DimFunc({Fn ()})"; } + public override string ToString () { return $"DimFunc({Fn (View)})"; } + + internal override int GetAnchor (int size) + { + View?.Layout (); - internal override int GetAnchor (int size) { return Fn (); } -} \ No newline at end of file + return Fn (View); + } +} diff --git a/Terminal.Gui/ViewBase/Layout/DimFuncWithView.cs b/Terminal.Gui/ViewBase/Layout/DimFuncWithView.cs deleted file mode 100644 index 2ff16bc9f2..0000000000 --- a/Terminal.Gui/ViewBase/Layout/DimFuncWithView.cs +++ /dev/null @@ -1,35 +0,0 @@ -#nullable enable -namespace Terminal.Gui.ViewBase; - -/// -/// Represents a function object that computes the dimension based on the passed view and by -/// executing the provided function. -/// -/// -/// 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. If this function throws ... -/// The returned from the function based on the passed view. -public record DimFuncWithView (Func Fn, View? View) : Dim -{ - /// - /// Gets the function that computes the dimension. - /// - public Func Fn { get; } = Fn; - - /// - /// Gets the passed view that the dimension is based on. - /// - public View View { get; } = View ?? throw new ArgumentNullException (nameof (View), @"View cannot be null"); - - /// - public override string ToString () { return $"DimFuncWithView({Fn (View)})"; } - - internal override int GetAnchor (int size) - { - View?.Layout (); - - return Fn (View!); - } -} \ No newline at end of file diff --git a/Terminal.Gui/ViewBase/Layout/Pos.cs b/Terminal.Gui/ViewBase/Layout/Pos.cs index f213e0941f..09694adc39 100644 --- a/Terminal.Gui/ViewBase/Layout/Pos.cs +++ b/Terminal.Gui/ViewBase/Layout/Pos.cs @@ -220,12 +220,14 @@ public static Pos AnchorEnd (int offset) public static Pos Center () { return new PosCenter (); } /// - /// Creates a object that computes the position by executing the provided function. The function - /// will be called every time the position is needed. + /// Creates a object that computes the position based on the passed view and by executing the + /// provided function. + /// The function will be called every time the position is needed. /// /// The function to be executed. + /// The view where the data will be retrieved. /// The returned from the function. - public static Pos Func (Func function) { return new PosFunc (function); } + public static Pos Func (Func function, View? view = null) { return new PosFunc (function, view); } /// Creates a percentage object /// The percent object. diff --git a/Terminal.Gui/ViewBase/Layout/PosFunc.cs b/Terminal.Gui/ViewBase/Layout/PosFunc.cs index a55ff6647f..a3eb5f5891 100644 --- a/Terminal.Gui/ViewBase/Layout/PosFunc.cs +++ b/Terminal.Gui/ViewBase/Layout/PosFunc.cs @@ -4,11 +4,31 @@ namespace Terminal.Gui.ViewBase; /// /// Represents a position that is computed by executing a function that returns an integer position. /// -/// The function that computes the dimension. If this function throws ... -public record PosFunc (Func Fn) : Pos +/// +/// 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 position. If this function throws ... +/// The returned from the function based on the passed view. +public record PosFunc (Func Fn, View? View = null) : Pos { + /// + /// Gets the function that computes the position. + /// + public Func Fn { get; } = Fn; + + /// + /// Gets the passed view that the position is based on. + /// + public View? View { get; } = View; + /// - public override string ToString () { return $"PosFunc({Fn ()})"; } + public override string ToString () { return $"PosFunc({Fn (View)})"; } + + internal override int GetAnchor (int size) + { + View?.Layout (); - internal override int GetAnchor (int size) { return Fn (); } + return Fn (View); + } } \ No newline at end of file diff --git a/Terminal.Gui/ViewBase/View.ScrollBars.cs b/Terminal.Gui/ViewBase/View.ScrollBars.cs index 9f79bcf98c..4463299f93 100644 --- a/Terminal.Gui/ViewBase/View.ScrollBars.cs +++ b/Terminal.Gui/ViewBase/View.ScrollBars.cs @@ -73,7 +73,7 @@ private void ConfigureVerticalScrollBar (ScrollBar scrollBar) scrollBar.Height = Dim.Fill ( Dim.Func ( - () => + _ => { if (_horizontalScrollBar.IsValueCreated) { @@ -98,7 +98,7 @@ private void ConfigureHorizontalScrollBar (ScrollBar scrollBar) scrollBar.Width = Dim.Fill ( Dim.Func ( - () => + _ => { if (_verticalScrollBar.IsValueCreated) { diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.cs index ae1f7dfea4..deaacbb0b6 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.cs @@ -154,7 +154,7 @@ internal FileDialog (IFileSystem fileSystem) X = 0, Y = Pos.Bottom (_btnBack), Width = Dim.Fill (), - Height = Dim.Fill (Dim.Func (() => IsInitialized ? _btnOk.Frame.Height : 1)) + Height = Dim.Fill (Dim.Func (_ => IsInitialized ? _btnOk.Frame.Height : 1)) }; Initialized += (s, e) => @@ -754,7 +754,7 @@ private string AspectGetter (object o) return (Style.IconProvider.GetIconWithOptionalSpace (fsi) + fsi.Name).Trim (); } - private int CalculateOkButtonPosX () + private int CalculateOkButtonPosX (View? _) { if (!IsInitialized || !_btnOk.IsInitialized || !_btnCancel.IsInitialized) { diff --git a/Terminal.Gui/Views/Menu/Menuv2.cs b/Terminal.Gui/Views/Menu/Menuv2.cs index 7a0b0b1f18..1eb5f6cc49 100644 --- a/Terminal.Gui/Views/Menu/Menuv2.cs +++ b/Terminal.Gui/Views/Menu/Menuv2.cs @@ -93,8 +93,8 @@ void MenuItemOnAccepted (object? sender, CommandEventArgs e) } case Line line: // Grow line so we get auto-join line - line.X = Pos.Func (() => -Border!.Thickness.Left); - line.Width = Dim.Fill ()! + Dim.Func (() => Border!.Thickness.Right); + line.X = Pos.Func (_ => -Border!.Thickness.Left); + line.Width = Dim.Fill ()! + Dim.Func (_ => Border!.Thickness.Right); break; } diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index 55a8654414..478d795654 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -394,12 +394,12 @@ params string [] buttons }; d.Width = Dim.Auto (DimAutoStyle.Auto, - minimumContentDim: Dim.Func (() => (int)((Application.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * (DefaultMinimumWidth / 100f))), - maximumContentDim: Dim.Func (() => (int)((Application.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * 0.9f))); + minimumContentDim: Dim.Func (_ => (int)((Application.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * (DefaultMinimumWidth / 100f))), + maximumContentDim: Dim.Func (_ => (int)((Application.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * 0.9f))); d.Height = Dim.Auto (DimAutoStyle.Auto, - minimumContentDim: Dim.Func (() => (int)((Application.Screen.Height - d.GetAdornmentsThickness ().Vertical) * (DefaultMinimumHeight / 100f))), - maximumContentDim: Dim.Func (() => (int)((Application.Screen.Height - d.GetAdornmentsThickness ().Vertical) * 0.9f))); + minimumContentDim: Dim.Func (_ => (int)((Application.Screen.Height - d.GetAdornmentsThickness ().Vertical) * (DefaultMinimumHeight / 100f))), + maximumContentDim: Dim.Func (_ => (int)((Application.Screen.Height - d.GetAdornmentsThickness ().Vertical) * 0.9f))); if (width != 0) diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index bb896db57b..cfa5bbc6c3 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -62,7 +62,7 @@ public NumericUpDown () Text = Value?.ToString () ?? "Err", X = Pos.Right (_down), Y = Pos.Top (_down), - Width = Dim.Auto (minimumContentDim: Dim.Func (() => string.Format (Format, Value).GetColumns())), + Width = Dim.Auto (minimumContentDim: Dim.Func (_ => string.Format (Format, Value).GetColumns())), Height = 1, TextAlignment = Alignment.Center, CanFocus = true, diff --git a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs index 7aadd28499..e6b1eb5908 100644 --- a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs +++ b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs @@ -40,11 +40,11 @@ public ScrollBar () // Set the default width and height based on the orientation - fill Viewport Width = Dim.Auto ( DimAutoStyle.Content, - Dim.Func (() => Orientation == Orientation.Vertical ? 1 : SuperView?.Viewport.Width ?? 0)); + Dim.Func (_ => Orientation == Orientation.Vertical ? 1 : SuperView?.Viewport.Width ?? 0)); Height = Dim.Auto ( DimAutoStyle.Content, - Dim.Func (() => Orientation == Orientation.Vertical ? SuperView?.Viewport.Height ?? 0 : 1)); + Dim.Func (_ => Orientation == Orientation.Vertical ? SuperView?.Viewport.Height ?? 0 : 1)); _decreaseButton = new () { diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 371245ba09..e4fee2f2a2 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -106,8 +106,8 @@ internal Dim GetWidthDimAuto () { return Dim.Auto ( DimAutoStyle.Content, - minimumContentDim: Dim.Func (() => _minimumNaturalWidth ?? 0), - maximumContentDim: Dim.Func (() => _minimumNaturalWidth ?? 0))!; + minimumContentDim: Dim.Func (_ => _minimumNaturalWidth ?? 0), + maximumContentDim: Dim.Func (_ => _minimumNaturalWidth ?? 0))!; } private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; @@ -540,7 +540,7 @@ private void SetHelpViewDefaultLayout () HelpView.X = Pos.Align (Alignment.End, AlignmentModes); _maxHelpWidth = HelpView.Text.GetColumns (); - HelpView.Width = Dim.Auto (DimAutoStyle.Text, maximumContentDim: Dim.Func ((() => _maxHelpWidth))); + HelpView.Width = Dim.Auto (DimAutoStyle.Text, maximumContentDim: Dim.Func ((_ => _maxHelpWidth))); HelpView.Height = Dim.Fill (); HelpView.Visible = true; @@ -672,7 +672,7 @@ private void SetKeyViewDefaultLayout () } KeyView.X = Pos.Align (Alignment.End, AlignmentModes); - KeyView.Width = Dim.Auto (DimAutoStyle.Text, minimumContentDim: Dim.Func (() => MinimumKeyTextSize)); + KeyView.Width = Dim.Auto (DimAutoStyle.Text, minimumContentDim: Dim.Func (_ => MinimumKeyTextSize)); KeyView.Height = Dim.Fill (); KeyView.Visible = true; diff --git a/Terminal.Gui/Views/Wizard/Wizard.cs b/Terminal.Gui/Views/Wizard/Wizard.cs index 7fc288b726..3e00e22bba 100644 --- a/Terminal.Gui/Views/Wizard/Wizard.cs +++ b/Terminal.Gui/Views/Wizard/Wizard.cs @@ -490,7 +490,7 @@ private void SizeStep (WizardStep step) step.Height = Dim.Fill ( Dim.Func ( - () => IsInitialized + v => IsInitialized ? SubViews.First (view => view.Y.Has (out _)).Frame.Height + 1 : 1)); // for button frame (+1 for lineView) step.Width = Dim.Fill (); @@ -502,9 +502,9 @@ private void SizeStep (WizardStep step) step.Height = Dim.Fill ( Dim.Func ( - () => IsInitialized - ? SubViews.First (view => view.Y.Has (out _)).Frame.Height + 1 - : 2)); // for button frame (+1 for lineView) + v => IsInitialized + ? SubViews.First (view => view.Y.Has (out _)).Frame.Height + 1 + : 2)); // for button frame (+1 for lineView) step.Width = Dim.Fill (); } } diff --git a/Tests/UnitTests/Views/LabelTests.cs b/Tests/UnitTests/Views/LabelTests.cs index 57c8a214d5..8f2484e3de 100644 --- a/Tests/UnitTests/Views/LabelTests.cs +++ b/Tests/UnitTests/Views/LabelTests.cs @@ -94,7 +94,7 @@ public void HotKey_Command_Does_Not_Accept () public void Text_Set_With_AnchorEnd_Works () { var label = new Label { Y = Pos.Center (), Text = "Say Hello ä½ " }; - label.X = Pos.AnchorEnd (0) - Pos.Func (() => label.TextFormatter.Text.GetColumns ()); + label.X = Pos.AnchorEnd (0) - Pos.Func (_ => label.TextFormatter.Text.GetColumns ()); var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; win.Add (label); diff --git a/Tests/UnitTests/Views/TileViewTests.cs b/Tests/UnitTests/Views/TileViewTests.cs index f46051b6b5..2c0306c4fe 100644 --- a/Tests/UnitTests/Views/TileViewTests.cs +++ b/Tests/UnitTests/Views/TileViewTests.cs @@ -1614,7 +1614,7 @@ public void TestTileView_CannotSetSplitterPosToFuncEtc () var ex = Assert.Throws (() => tileView.SetSplitterPos (0, Pos.Right (tileView))); Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosView", ex.Message); - ex = Assert.Throws (() => tileView.SetSplitterPos (0, Pos.Func (() => 1))); + ex = Assert.Throws (() => tileView.SetSplitterPos (0, Pos.Func (_ => 1))); Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosFunc", ex.Message); // Also not allowed because this results in a PosCombine diff --git a/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.DimTypes.cs b/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.DimTypes.cs index c5c2d3b9d3..002375d3c9 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.DimTypes.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.DimTypes.cs @@ -179,7 +179,7 @@ public void With_SubView_Using_DimFill_And_Another_SubView (int minWidth, int ma public void With_SubView_Using_DimFunc () { var view = new View (); - var subview = new View { Width = Dim.Func (() => 20), Height = Dim.Func (() => 25) }; + var subview = new View { Width = Dim.Func (_ => 20), Height = Dim.Func (_ => 25) }; view.Add (subview); subview.SetRelativeLayout (new (100, 100)); diff --git a/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs b/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs index 7bb7bf950a..5e3b6ffee4 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs @@ -645,7 +645,7 @@ public void With_SubView_Using_PosFunc () Width = Dim.Auto (), Height = Dim.Auto (), }; - var subview = new View { X = Pos.Func (() => 20), Y = Pos.Func (() => 25) }; + var subview = new View { X = Pos.Func (_ => 20), Y = Pos.Func (_ => 25) }; view.Add (subview); view.SetRelativeLayout (new (100, 100)); diff --git a/Tests/UnitTestsParallelizable/View/Layout/Dim.FillTests.cs b/Tests/UnitTestsParallelizable/View/Layout/Dim.FillTests.cs index ec43c95389..ea93099f45 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/Dim.FillTests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/Dim.FillTests.cs @@ -123,12 +123,12 @@ public void DimFill_SetsValue () [Fact] public void DimFill_Margin_Is_Dim_SetsValue () { - Dim testMargin = Dim.Func (() => 0); + Dim testMargin = Dim.Func (_ => 0); Dim dim = Dim.Fill (testMargin); Assert.Equal (0, dim!.GetAnchor (0)); - testMargin = Dim.Func (() => 5); + testMargin = Dim.Func (_ => 5); dim = Dim.Fill (testMargin); Assert.Equal (-5, dim!.GetAnchor (0)); } diff --git a/Tests/UnitTestsParallelizable/View/Layout/Dim.FuncTests.cs b/Tests/UnitTestsParallelizable/View/Layout/Dim.FuncTests.cs index 7dfc2a7bb6..a94a858cba 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/Dim.FuncTests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/Dim.FuncTests.cs @@ -10,8 +10,8 @@ public class DimFuncTests (ITestOutputHelper output) [Fact] public void DimFunc_Equal () { - Func f1 = () => 0; - Func f2 = () => 0; + Func f1 = _ => 0; + Func f2 = _ => 0; Dim dim1 = Func (f1); Dim dim2 = Func (f1); @@ -20,7 +20,7 @@ public void DimFunc_Equal () dim2 = Func (f2); Assert.NotEqual (dim1, dim2); - f2 = () => 1; + f2 = _ => 1; dim2 = Func (f2); Assert.NotEqual (dim1, dim2); } @@ -29,7 +29,7 @@ public void DimFunc_Equal () public void DimFunc_SetsValue () { var text = "Test"; - Dim dim = Func (() => text.Length); + Dim dim = Func (_ => text.Length); Assert.Equal ("DimFunc(4)", dim.ToString ()); text = "New Test"; @@ -42,8 +42,51 @@ public void DimFunc_SetsValue () [Fact] public void DimFunc_Calculate_ReturnsCorrectValue () { - var dim = new DimFunc (() => 10); + var dim = new DimFunc (_ => 10); int result = dim.Calculate (0, 100, null, Dimension.None); Assert.Equal (10, result); } + + [Fact] + public void DimFunc_View_Equal () + { + Func f1 = v => v.Frame.Width; + Func f2 = v => v.Frame.Width; + View view1 = new (); + View view2 = new (); + + Dim dim1 = Func (f1, view1); + Dim dim2 = Func (f1, view1); + Assert.Equal (dim1, dim2); + + dim2 = Func (f2, view2); + Assert.NotEqual (dim1, dim2); + + view2.Width = 1; + Assert.NotEqual (dim1, dim2); + Assert.Equal (1, f2 (view2)); + } + + [Fact] + public void DimFunc_View_SetsValue () + { + View view = new () { Text = "Test" }; + Dim dim = Func (v => v.Text.Length, view); + Assert.Equal ("DimFunc(4)", dim.ToString ()); + + view.Text = "New Test"; + Assert.Equal ("DimFunc(8)", dim.ToString ()); + + view.Text = ""; + Assert.Equal ("DimFunc(0)", dim.ToString ()); + } + + [Fact] + public void DimFunc_View_Calculate_ReturnsCorrectValue () + { + View view = new () { Width = 10 }; + var dim = new DimFunc (v => v.Frame.Width, view); + int result = dim.Calculate (0, 100, view, Dimension.None); + Assert.Equal (10, result); + } } diff --git a/Tests/UnitTestsParallelizable/View/Layout/Dim.FuncWithViewTests.cs b/Tests/UnitTestsParallelizable/View/Layout/Dim.FuncWithViewTests.cs deleted file mode 100644 index 75e3a1bf95..0000000000 --- a/Tests/UnitTestsParallelizable/View/Layout/Dim.FuncWithViewTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Xunit.Abstractions; -using static Terminal.Gui.ViewBase.Dim; - -namespace Terminal.Gui.LayoutTests; - -public class DimFuncWithViewTests (ITestOutputHelper output) -{ - private readonly ITestOutputHelper _output = output; - - [Fact] - public void DimFuncWithView_Equal () - { - Func f1 = v => v.Frame.Width; - Func f2 = v => v.Frame.Width; - View view1 = new (); - View view2 = new (); - - Dim dim1 = FuncWithView (f1, view1); - Dim dim2 = FuncWithView (f1, view1); - Assert.Equal (dim1, dim2); - - dim2 = FuncWithView (f2, view2); - Assert.NotEqual (dim1, dim2); - - view2.Width = 1; - Assert.NotEqual (dim1, dim2); - Assert.Equal (1, f2 (view2)); - } - - [Fact] - public void DimFuncWithView_SetsValue () - { - View view = new () { Text = "Test" }; - Dim dim = FuncWithView (v => v.Text.Length, view); - Assert.Equal ("DimFuncWithView(4)", dim.ToString ()); - - view.Text = "New Test"; - Assert.Equal ("DimFuncWithView(8)", dim.ToString ()); - - view.Text = ""; - Assert.Equal ("DimFuncWithView(0)", dim.ToString ()); - } - - [Fact] - public void DimFuncWithView_Calculate_ReturnsCorrectValue () - { - View view = new () { Width = 10 }; - var dim = new DimFuncWithView (v => v.Frame.Width, view); - int result = dim.Calculate (0, 100, view, Dimension.None); - Assert.Equal (10, result); - } - - [Fact] - public void DimFuncWithView_Throws_ArgumentNullException_If_View_Is_Null () - { - Assert.Throws (() => FuncWithView (v => 0, null)); - } -} diff --git a/Tests/UnitTestsParallelizable/View/Layout/FrameTests.cs b/Tests/UnitTestsParallelizable/View/Layout/FrameTests.cs index a58475158f..c6d1fd9194 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/FrameTests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/FrameTests.cs @@ -61,10 +61,10 @@ public void Frame_Empty_Initializer_Overrides_Base () Assert.Equal (view.Height, frame.Height); // Set back to original state - view.X = Pos.Func (() => 10); - view.Y = Pos.Func (() => 20); - view.Width = Dim.Func (() => 30); - view.Height = Dim.Func (() => 40); + view.X = Pos.Func (_ => 10); + view.Y = Pos.Func (_ => 20); + view.Width = Dim.Func (_ => 30); + view.Height = Dim.Func (_ => 40); Assert.True (view.NeedsLayout); view.Layout (); @@ -281,10 +281,10 @@ private class FrameTestView : View { public FrameTestView () { - X = Pos.Func (() => 10); - Y = Pos.Func (() => 20); - Width = Dim.Func (() => 30); - Height = Dim.Func (() => 40); + X = Pos.Func (_ => 10); + Y = Pos.Func (_ => 20); + Width = Dim.Func (_ => 30); + Height = Dim.Func (_ => 40); } } diff --git a/Tests/UnitTestsParallelizable/View/Layout/Pos.FuncTests.cs b/Tests/UnitTestsParallelizable/View/Layout/Pos.FuncTests.cs index 9874c91602..9665aceed1 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/Pos.FuncTests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/Pos.FuncTests.cs @@ -9,14 +9,14 @@ public class PosFuncTests (ITestOutputHelper output) [Fact] public void PosFunc_Equal () { - Func f1 = () => 0; - Func f2 = () => 0; + Func f1 = _ => 0; + Func f2 = _ => 0; Pos pos1 = Pos.Func (f1); Pos pos2 = Pos.Func (f1); Assert.Equal (pos1, pos2); - f2 = () => 1; + f2 = _ => 1; pos2 = Pos.Func (f2); Assert.NotEqual (pos1, pos2); } @@ -25,7 +25,7 @@ public void PosFunc_Equal () public void PosFunc_SetsValue () { var text = "Test"; - Pos pos = Pos.Func (() => text.Length); + Pos pos = Pos.Func (_ => text.Length); Assert.Equal ("PosFunc(4)", pos.ToString ()); text = "New Test"; @@ -38,8 +38,52 @@ public void PosFunc_SetsValue () [Fact] public void PosFunc_Calculate_ReturnsCorrectValue () { - var pos = new PosFunc (() => 10); + var pos = new PosFunc (_ => 10); int result = pos.Calculate (0, 100, null, Dimension.None); Assert.Equal (10, result); } + + [Fact] + public void PosFunc_View_Equal () + { + Func f1 = v => v.Frame.X; + Func f2 = v => v.Frame.X; + View view1 = new (); + View view2 = new (); + + Pos pos1 = Pos.Func (f1, view1); + Pos pos2 = Pos.Func (f1, view1); + Assert.Equal (pos1, pos2); + + f2 = _ => 1; + pos2 = Pos.Func (f2, view2); + Assert.NotEqual (pos1, pos2); + + view2.X = 1; + Assert.NotEqual (pos1, pos2); + Assert.Equal (1, f2 (view2)); + } + + [Fact] + public void PosFunc_View_SetsValue () + { + View view = new () { Text = "Test" }; + Pos pos = Pos.Func (v => v.Text.Length, view); + Assert.Equal ("PosFunc(4)", pos.ToString ()); + + view.Text = "New Test"; + Assert.Equal ("PosFunc(8)", pos.ToString ()); + + view.Text = ""; + Assert.Equal ("PosFunc(0)", pos.ToString ()); + } + + [Fact] + public void PosFunc_View_Calculate_ReturnsCorrectValue () + { + View view = new () { X = 10 }; + var pos = new PosFunc (v => v.Frame.X, view); + int result = pos.Calculate (0, 100, view, Dimension.None); + Assert.Equal (10, result); + } } diff --git a/Tests/UnitTestsParallelizable/View/Layout/Pos.Tests.cs b/Tests/UnitTestsParallelizable/View/Layout/Pos.Tests.cs index fcfc40456e..b37cf622f9 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/Pos.Tests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/Pos.Tests.cs @@ -21,7 +21,7 @@ public void PosFactor_Calculate_ReturnsExpectedValue () [Fact] public void PosFunc_Calculate_ReturnsExpectedValue () { - var posFunc = new PosFunc (() => 5); + var posFunc = new PosFunc (_ => 5); int result = posFunc.Calculate (10, new DimAbsolute (2), null, Dimension.None); Assert.Equal (5, result); } @@ -86,7 +86,7 @@ public void PosCombine_DoesNotReturn () public void PosFunction_SetsValue () { var text = "Test"; - Pos pos = Pos.Func (() => text.Length); + Pos pos = Pos.Func (_ => text.Length); Assert.Equal ("PosFunc(4)", pos.ToString ()); text = "New Test"; diff --git a/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs b/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs index f8a5607874..b42b3a046c 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs @@ -597,7 +597,7 @@ public void Set_Height_Non_DimAbsolute_Explicit_Layout_Required () Assert.True (v.NeedsLayout); Assert.Equal (0, v.Frame.Height); - v.Height = Dim.Func (() => 10); + v.Height = Dim.Func (_ => 10); Assert.True (v.NeedsLayout); Assert.Equal (0, v.Frame.Height); @@ -649,7 +649,7 @@ public void Set_Width_Non_DimAbsolute_Explicit_Layout_Required () Assert.True (v.NeedsLayout); Assert.Equal (0, v.Frame.Width); - v.Width = Dim.Func (() => 10); + v.Width = Dim.Func (_ => 10); Assert.True (v.NeedsLayout); Assert.Equal (0, v.Frame.Width); @@ -675,7 +675,7 @@ public void Set_X_Non_PosAbsolute_Explicit_Layout_Required () Assert.True (v.NeedsLayout); Assert.Equal (0, v.Frame.X); - v.X = Pos.Func (() => 10); + v.X = Pos.Func (_ => 10); Assert.True (v.NeedsLayout); Assert.Equal (0, v.Frame.X); @@ -731,7 +731,7 @@ public void Set_Y_Non_PosAbsolute_Explicit_Layout_Required () Assert.True (v.NeedsLayout); Assert.Equal (0, v.Frame.Y); - v.Y = Pos.Func (() => 10); + v.Y = Pos.Func (_ => 10); Assert.True (v.NeedsLayout); Assert.Equal (0, v.Frame.Y); diff --git a/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs b/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs index 38be25a021..073e8c85dc 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs @@ -1,4 +1,5 @@ -using UnitTests; +using JetBrains.Annotations; +using UnitTests; using Xunit.Abstractions; using static Terminal.Gui.ViewBase.Dim; @@ -403,7 +404,7 @@ public void PosDimFunc () }; view.X = Pos.AnchorEnd (0) - Pos.Func (GetViewWidth); - int GetViewWidth () { return view.Frame.Width; } + int GetViewWidth ([CanBeNull] View _) { return view.Frame.Width; } // view will be 3 chars wide. It's X will be 27 (30 - 3). // BUGBUG: IsInitialized need to be true before calculate From 3398222a440e006db5e1a16bac8f943f15ea4f11 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 25 Jul 2025 18:43:18 +0100 Subject: [PATCH 07/34] Trying fix unit test error --- Terminal.Gui/Views/Menuv1/Menu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Menuv1/Menu.cs b/Terminal.Gui/Views/Menuv1/Menu.cs index 8969595acd..c4579aab35 100644 --- a/Terminal.Gui/Views/Menuv1/Menu.cs +++ b/Terminal.Gui/Views/Menuv1/Menu.cs @@ -527,7 +527,7 @@ private void Application_RootMouseEvent (object? sender, MouseEventArgs a) private void Application_UnGrabbedMouse (object? sender, ViewEventArgs a) { - if (_host.IsMenuOpen) + if (_host is { IsMenuOpen: true }) { _host.CloseAllMenus (); } From 384cab8c62e55f10aec907ca36962798d109ec89 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 27 Jul 2025 03:07:06 +0100 Subject: [PATCH 08/34] Revert layout changes --- Terminal.Gui/ViewBase/View.cs | 7 +++++ .../View/Adornment/ShadowStyleTests.cs | 1 - .../UnitTests/View/Draw/ClearViewportTests.cs | 4 +-- Tests/UnitTests/View/Draw/ClipTests.cs | 4 +-- .../View/Layout/GetViewsUnderLocationTests.cs | 3 -- Tests/UnitTests/View/Mouse/MouseTests.cs | 1 - Tests/UnitTests/View/TextTests.cs | 2 -- Tests/UnitTests/View/ViewTests.cs | 1 - Tests/UnitTests/Views/ButtonTests.cs | 1 - Tests/UnitTests/Views/ColorPicker16Tests.cs | 2 +- Tests/UnitTests/Views/ColorPickerTests.cs | 2 +- Tests/UnitTests/Views/LabelTests.cs | 2 +- .../UnitTests/Views/Menuv1/MenuBarv1Tests.cs | 1 - Tests/UnitTests/Views/ProgressBarTests.cs | 8 ++--- Tests/UnitTests/Views/SliderTests.cs | 3 -- Tests/UnitTests/Views/TextFieldTests.cs | 1 - .../View/Layout/Dim.AutoTests.PosTypes.cs | 6 ---- .../View/Layout/SetLayoutTests.cs | 30 +++++++++---------- .../View/Layout/SetRelativeLayoutTests.cs | 1 - .../UnitTestsParallelizable/View/TextTests.cs | 1 - .../Views/TextFieldTests.cs | 1 - 21 files changed, 32 insertions(+), 50 deletions(-) diff --git a/Terminal.Gui/ViewBase/View.cs b/Terminal.Gui/ViewBase/View.cs index 75b9d9d4bb..720f0d4287 100644 --- a/Terminal.Gui/ViewBase/View.cs +++ b/Terminal.Gui/ViewBase/View.cs @@ -245,6 +245,13 @@ public virtual void EndInit () } } + // Force a layout each time a View is initialized + // See: https://github.com/gui-cs/Terminal.Gui/issues/3951 + // See: https://github.com/gui-cs/Terminal.Gui/issues/4204 + Layout (); // the EventLog in AllViewsTester fails to layout correctly if this is not here (convoluted Dim.Fill(Func)). + + // Complex layout scenarios (e.g. DimAuto and PosAlign) may require multiple layouts to be performed. + // Thus, we call SetNeedsLayout() to ensure that the layout is performed at least once. SetNeedsLayout (); Initialized?.Invoke (this, EventArgs.Empty); diff --git a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs index 2c9dc6d666..6d45ac9b75 100644 --- a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs +++ b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs @@ -152,7 +152,6 @@ public void ShadowStyle_Button1Pressed_Causes_Movement (ShadowStyle style, int e superView.Add (view); superView.BeginInit (); superView.EndInit (); - superView.Layout (); Thickness origThickness = view.Margin!.Thickness; view.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (0, 0) }); diff --git a/Tests/UnitTests/View/Draw/ClearViewportTests.cs b/Tests/UnitTests/View/Draw/ClearViewportTests.cs index 0b46dbd0b3..94a2117ecb 100644 --- a/Tests/UnitTests/View/Draw/ClearViewportTests.cs +++ b/Tests/UnitTests/View/Draw/ClearViewportTests.cs @@ -113,7 +113,7 @@ public void Clear_ClearsEntireViewport () superView.Add (view); superView.BeginInit (); superView.EndInit (); - superView.Layout (); + superView.LayoutSubViews (); superView.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -162,7 +162,7 @@ public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly () superView.Add (view); superView.BeginInit (); superView.EndInit (); - superView.Layout (); + superView.LayoutSubViews (); superView.Draw (); diff --git a/Tests/UnitTests/View/Draw/ClipTests.cs b/Tests/UnitTests/View/Draw/ClipTests.cs index 744002666b..893900e59b 100644 --- a/Tests/UnitTests/View/Draw/ClipTests.cs +++ b/Tests/UnitTests/View/Draw/ClipTests.cs @@ -79,7 +79,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) superView.Add (view); superView.BeginInit (); superView.EndInit (); - superView.Layout (); + superView.LayoutSubViews (); superView.Draw (); @@ -258,7 +258,6 @@ public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped () view.Border!.Thickness = new (1); view.BeginInit (); view.EndInit (); - view.Layout (); Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ()); // Act @@ -292,7 +291,6 @@ public void SetClip_Default_ClipsToViewport () view.Border!.Thickness = new (1); view.BeginInit (); view.EndInit (); - view.Layout (); Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ()); view.Viewport = view.Viewport with { X = 1, Y = 1 }; diff --git a/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs b/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs index cae736429c..03e6ad28f0 100644 --- a/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs +++ b/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs @@ -327,7 +327,6 @@ public void Returns_Correct_If_Start_Has_Adornment_WithSubView (int testX, int t Application.Top.Padding.Add (subview); Application.Top.BeginInit (); Application.Top.EndInit (); - Application.Top.LayoutSubViews (); View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); @@ -490,7 +489,6 @@ public void Returns_Correct_If_SubView_Has_Adornment_WithSubView (int testX, int Application.Top.Add (subview); Application.Top.BeginInit (); Application.Top.EndInit (); - Application.Top.LayoutSubViews (); View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); @@ -542,7 +540,6 @@ public void Returns_Correct_If_SubView_Is_Scrolled_And_Has_Adornment_WithSubView Application.Top.Add (subview); Application.Top.BeginInit (); Application.Top.EndInit (); - Application.Top.LayoutSubViews (); View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); diff --git a/Tests/UnitTests/View/Mouse/MouseTests.cs b/Tests/UnitTests/View/Mouse/MouseTests.cs index 0718cb4f66..6d8a68eab3 100644 --- a/Tests/UnitTests/View/Mouse/MouseTests.cs +++ b/Tests/UnitTests/View/Mouse/MouseTests.cs @@ -191,7 +191,6 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_B // When mouse is held down me.Flags = pressed; - view.Layout (); view.NewMouseEvent (me); Assert.Equal (0, clickedCount); me.Handled = false; diff --git a/Tests/UnitTests/View/TextTests.cs b/Tests/UnitTests/View/TextTests.cs index 4c342f4ee1..4a75be09a0 100644 --- a/Tests/UnitTests/View/TextTests.cs +++ b/Tests/UnitTests/View/TextTests.cs @@ -1024,7 +1024,6 @@ public void Narrow_Wide_Runes () top.Add (frame); top.BeginInit (); top.EndInit (); - top.Layout (); Assert.Equal (new (0, 0, 20, 1), horizontalView.Frame); Assert.Equal (new (0, 3, 1, 20), verticalView.Frame); @@ -1128,7 +1127,6 @@ public void SetText_RendersCorrectly () view = new Label { Text = text }; view.BeginInit (); view.EndInit (); - view.Layout (); view.Draw (); DriverAssert.AssertDriverContentsWithFrameAre (text, output); diff --git a/Tests/UnitTests/View/ViewTests.cs b/Tests/UnitTests/View/ViewTests.cs index 0493476fd7..67c52e133c 100644 --- a/Tests/UnitTests/View/ViewTests.cs +++ b/Tests/UnitTests/View/ViewTests.cs @@ -291,7 +291,6 @@ public void New_Initializes () r.BeginInit (); r.EndInit (); - r.Layout (); Assert.False (r.CanFocus); Assert.False (r.HasFocus); Assert.Equal (new (0, 0, 1, 13), r.Viewport); diff --git a/Tests/UnitTests/Views/ButtonTests.cs b/Tests/UnitTests/Views/ButtonTests.cs index 7c9f9aed9c..2e84f96ab7 100644 --- a/Tests/UnitTests/Views/ButtonTests.cs +++ b/Tests/UnitTests/Views/ButtonTests.cs @@ -232,7 +232,6 @@ public void Constructors_Defaults () Assert.True (btn.CanFocus); Application.Driver?.ClearContents (); - btn.Layout (); btn.Draw (); expected = @$" diff --git a/Tests/UnitTests/Views/ColorPicker16Tests.cs b/Tests/UnitTests/Views/ColorPicker16Tests.cs index 23e2af7257..53182a950b 100644 --- a/Tests/UnitTests/Views/ColorPicker16Tests.cs +++ b/Tests/UnitTests/Views/ColorPicker16Tests.cs @@ -14,7 +14,7 @@ public void Constructors () colorPicker.BeginInit (); colorPicker.EndInit (); - colorPicker.Layout (); + colorPicker.LayoutSubViews (); Assert.Equal (new (0, 0, 32, 4), colorPicker.Frame); } diff --git a/Tests/UnitTests/Views/ColorPickerTests.cs b/Tests/UnitTests/Views/ColorPickerTests.cs index f6f88db9b3..22144245de 100644 --- a/Tests/UnitTests/Views/ColorPickerTests.cs +++ b/Tests/UnitTests/Views/ColorPickerTests.cs @@ -675,7 +675,7 @@ public void ColorPicker_SwitchingColorModels_ResetsBars () // Switch to HSV cp.Style.ColorModel = ColorModel.HSV; cp.ApplyStyleChanges (); - cp.Layout (); + cp.Draw (); ColorBar h = GetColorBar (cp, ColorPickerPart.Bar1); diff --git a/Tests/UnitTests/Views/LabelTests.cs b/Tests/UnitTests/Views/LabelTests.cs index 8f2484e3de..582c457474 100644 --- a/Tests/UnitTests/Views/LabelTests.cs +++ b/Tests/UnitTests/Views/LabelTests.cs @@ -1053,7 +1053,7 @@ public void Label_Height_Zero_Stays_Zero () win.Add (label); win.BeginInit (); win.EndInit (); - win.Layout (); + win.LayoutSubViews (); win.Draw (); Assert.Equal (5, text.Length); diff --git a/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs b/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs index f3d31e6dd8..087823380e 100644 --- a/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs +++ b/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs @@ -2130,7 +2130,6 @@ public void MenuBar_Position_And_Size_With_HotKeys_Is_The_Same_As_Without_HotKey Assert.True (menu.NewKeyDownEvent (menu.Key)); Assert.True (menu.IsMenuOpen); View.SetClipToScreen (); - top.Layout (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (0), output); diff --git a/Tests/UnitTests/Views/ProgressBarTests.cs b/Tests/UnitTests/Views/ProgressBarTests.cs index f594086531..744ce198f9 100644 --- a/Tests/UnitTests/Views/ProgressBarTests.cs +++ b/Tests/UnitTests/Views/ProgressBarTests.cs @@ -12,7 +12,7 @@ public void Default_Constructor () var pb = new ProgressBar (); pb.BeginInit (); pb.EndInit (); - pb.Layout (); + Assert.False (pb.CanFocus); Assert.Equal (0, pb.Fraction); @@ -32,7 +32,7 @@ public void Fraction_Redraw () pb.BeginInit (); pb.EndInit (); - pb.Layout (); + pb.LayoutSubViews (); for (var i = 0; i <= pb.Frame.Width; i++) { @@ -174,7 +174,7 @@ public void Pulse_Redraw_BidirectionalMarquee_False () pb.BeginInit (); pb.EndInit (); - pb.Layout (); + pb.LayoutSubViews (); for (var i = 0; i < 38; i++) { @@ -879,7 +879,7 @@ public void Pulse_Redraw_BidirectionalMarquee_True_Default () pb.BeginInit (); pb.EndInit (); - pb.Layout (); + pb.LayoutSubViews (); for (var i = 0; i < 38; i++) { diff --git a/Tests/UnitTests/Views/SliderTests.cs b/Tests/UnitTests/Views/SliderTests.cs index afe3f1dc15..69c56e593e 100644 --- a/Tests/UnitTests/Views/SliderTests.cs +++ b/Tests/UnitTests/Views/SliderTests.cs @@ -514,7 +514,6 @@ private void DimAuto_Both_Respects_SuperView_ContentSize () view.Add (slider); view.BeginInit (); view.EndInit (); - view.Layout (); Size expectedSize = slider.Frame.Size; @@ -548,7 +547,6 @@ private void DimAuto_Width_Respects_SuperView_ContentSize () view.Add (slider); view.BeginInit (); view.EndInit (); - view.Layout (); Size expectedSize = slider.Frame.Size; @@ -582,7 +580,6 @@ private void DimAuto_Height_Respects_SuperView_ContentSize () view.Add (slider); view.BeginInit (); view.EndInit (); - view.Layout (); Size expectedSize = slider.Frame.Size; diff --git a/Tests/UnitTests/Views/TextFieldTests.cs b/Tests/UnitTests/Views/TextFieldTests.cs index a42dff91e9..822871a55a 100644 --- a/Tests/UnitTests/Views/TextFieldTests.cs +++ b/Tests/UnitTests/Views/TextFieldTests.cs @@ -1673,7 +1673,6 @@ public void Draw_Esc_Rune () var tf = new TextField { Width = 5, Text = "\u001b" }; tf.BeginInit (); tf.EndInit (); - tf.Layout (); tf.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ("\u241b", output); diff --git a/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs b/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs index 5e3b6ffee4..d14dfc7c09 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs @@ -263,7 +263,6 @@ public void With_SubView_Using_PosCenter_Combine (int minWidth, int maxWidth, in view.BeginInit (); view.EndInit (); - view.Layout (); // subview should be centered in the parent view + 1 Assert.Equal ((view.Viewport.Width - subview.Frame.Width) / 2 + 1, subview.Frame.X); @@ -339,7 +338,6 @@ public void With_SubView_Using_PosAnchorEnd (int minWidth, int maxWidth, int min view.BeginInit (); view.EndInit (); - view.Layout (); // subview should be at the end of the view Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); @@ -394,7 +392,6 @@ public void With_SubView_And_SubView_Using_PosAnchorEnd (int minWidth, int maxWi view.BeginInit (); view.EndInit (); - view.Layout (); // subview should be at the end of the view Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); @@ -449,7 +446,6 @@ public void With_DimAutoSubView_And_SubView_Using_PosAnchorEnd (int minWidth, in view.BeginInit (); view.EndInit (); - view.Layout (); // subview should be at the end of the view Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); @@ -512,7 +508,6 @@ public void With_PosViewSubView_And_SubView_Using_PosAnchorEnd (int minWidth, in view.BeginInit (); view.EndInit (); - view.Layout (); // subview should be at the end of the view Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); @@ -577,7 +572,6 @@ public void With_DimViewSubView_And_SubView_Using_PosAnchorEnd (int minWidth, in view.BeginInit (); view.EndInit (); - view.Layout (); // subview should be at the end of the view Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); diff --git a/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs b/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs index b42b3a046c..44a7309d98 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs @@ -376,24 +376,24 @@ public void LayoutSubViews_LayoutStarted_Complete () Assert.Equal (0, layoutStartedCount); Assert.Equal (0, layoutCompleteCount); - superView.EndInit (); // Only sets NeedsLayout - 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 (2, layoutStartedCount); + Assert.Equal (2, layoutCompleteCount); superView.LayoutSubViews (); - Assert.Equal (0, borderLayoutStartedCount); // No Border - Assert.Equal (0, borderLayoutCompleteCount); // No Border - Assert.Equal (1, layoutStartedCount); - Assert.Equal (1, layoutCompleteCount); + Assert.Equal (1, borderLayoutStartedCount); + Assert.Equal (1, borderLayoutCompleteCount); + Assert.Equal (3, layoutStartedCount); + Assert.Equal (3, layoutCompleteCount); superView.SetNeedsLayout (); superView.LayoutSubViews (); - Assert.Equal (0, borderLayoutStartedCount); // No Border - Assert.Equal (0, borderLayoutCompleteCount); // No Border - Assert.Equal (2, layoutStartedCount); - Assert.Equal (2, layoutCompleteCount); + Assert.Equal (1, borderLayoutStartedCount); + Assert.Equal (1, borderLayoutCompleteCount); + Assert.Equal (4, layoutStartedCount); + Assert.Equal (4, layoutCompleteCount); superView.Dispose (); } @@ -443,8 +443,8 @@ public void LayoutSubViews_Raises_LayoutStarted_LayoutComplete () superView.BeginInit (); superView.EndInit (); superView.LayoutSubViews (); - Assert.Equal (2, layoutStartedRaised); - Assert.Equal (2, layoutCompleteRaised); + Assert.Equal (3, layoutStartedRaised); + Assert.Equal (3, layoutCompleteRaised); } [Fact] diff --git a/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs b/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs index 073e8c85dc..bf032df2d8 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs @@ -410,7 +410,6 @@ public void PosDimFunc () // BUGBUG: IsInitialized need to be true before calculate view.BeginInit (); view.EndInit (); - view.Layout (); view.SetRelativeLayout (screen); Assert.Equal (27, view.Frame.X); Assert.Equal (0, view.Frame.Y); diff --git a/Tests/UnitTestsParallelizable/View/TextTests.cs b/Tests/UnitTestsParallelizable/View/TextTests.cs index f86fdcc39e..31ca7eea84 100644 --- a/Tests/UnitTestsParallelizable/View/TextTests.cs +++ b/Tests/UnitTestsParallelizable/View/TextTests.cs @@ -170,7 +170,6 @@ public void TextDirection_Horizontal_Dims_Correct_WidthAbsolute () }; view.BeginInit (); view.EndInit (); - view.Layout (); Assert.Equal (new (0, 0, 10, 1), view.Frame); Assert.Equal (new (0, 0, 10, 1), view.Viewport); diff --git a/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs b/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs index 664479259d..a38f6188ba 100644 --- a/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs +++ b/Tests/UnitTestsParallelizable/Views/TextFieldTests.cs @@ -543,7 +543,6 @@ public void PositionCursor_Respect_GetColumns () var tf = new TextField { Width = 5 }; tf.BeginInit (); tf.EndInit (); - tf.Layout (); tf.NewKeyDownEvent (new ("📄")); Assert.Equal (1, tf.CursorPosition); From 08dfea67cf07240a373e1a41963e1a938cfa2d9d Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 03:27:16 +0100 Subject: [PATCH 09/34] Fixes #4216. Legacy drivers aren't refreshing the screen correctly on view drag --- Terminal.Gui/App/Application.Run.cs | 2 +- Terminal.Gui/ViewBase/View.Drawing.cs | 43 +++++++++++++--- Terminal.Gui/ViewBase/View.Layout.cs | 5 +- .../View/Draw/NeedsDrawTests.cs | 50 ++++++++++++------- 4 files changed, 74 insertions(+), 26 deletions(-) diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index 30c1384a3e..7a359eb615 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -535,7 +535,7 @@ public static bool RunIteration (ref RunState state, bool firstIteration = false return firstIteration; } - LayoutAndDraw (); + LayoutAndDraw (TopLevels.Any (v => v.NeedsLayout || v.NeedsDraw)); if (PositionCursor ()) { diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index a8d2d4bf85..84f40c337b 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -1,5 +1,6 @@ #nullable enable using System.ComponentModel; +using System.Diagnostics; namespace Terminal.Gui.ViewBase; @@ -111,6 +112,26 @@ public void Draw (DrawContext? context = null) Border?.AdvanceDrawIndicator (); ClearNeedsDraw (); + + if (this is not Adornment && SuperView is not Adornment) + { + // Parent + Debug.Assert (Margin!.Parent == this); + Debug.Assert (Border!.Parent == this); + Debug.Assert (Padding!.Parent == this); + + // SubViewNeedsDraw is set to false by ClearNeedsDraw. + Debug.Assert (SubViewNeedsDraw == false); + Debug.Assert (Margin!.SubViewNeedsDraw == false); + Debug.Assert (Border!.SubViewNeedsDraw == false); + Debug.Assert (Padding!.SubViewNeedsDraw == false); + + // NeedsDraw is set to false by ClearNeedsDraw. + Debug.Assert (NeedsDraw == false); + Debug.Assert (Margin!.NeedsDraw == false); + Debug.Assert (Border!.NeedsDraw == false); + Debug.Assert (Padding!.NeedsDraw == false); + } } // ------------------------------------ @@ -131,6 +152,11 @@ public void Draw (DrawContext? context = null) private void DoDrawAdornmentsSubViews () { + if (Border?.NeedsLayout == true) + { + Border.Layout (); + } + // NOTE: We do not support subviews of Margin? if (Border?.SubViews is { } && Border.Thickness != Thickness.Empty) @@ -151,6 +177,11 @@ private void DoDrawAdornmentsSubViews () SetClip (saved); } + if (Padding?.NeedsLayout == true) + { + Padding.Layout (); + } + if (Padding?.SubViews is { } && Padding.Thickness != Thickness.Empty) { foreach (View subview in Padding.SubViews) @@ -720,8 +751,7 @@ protected virtual void OnDrawComplete (DrawContext? context) { } /// public bool NeedsDraw { - // TODO: Figure out if we can decouple NeedsDraw from NeedsLayout. - get => Visible && (NeedsDrawRect != Rectangle.Empty || NeedsLayout); + get => Visible && (NeedsDrawRect != Rectangle.Empty || Margin?.NeedsDraw == true || Border?.NeedsDraw == true || Padding?.NeedsDraw == true); set { if (value) @@ -807,7 +837,7 @@ public void SetNeedsDraw (Rectangle viewPortRelativeRegion) } // There was multiple enumeration error here, so calling ToArray - probably a stop gap - foreach (View subview in InternalSubViews) + foreach (View subview in InternalSubViews.ToArray ()) { if (subview.Frame.IntersectsWith (viewPortRelativeRegion)) { @@ -846,17 +876,17 @@ protected void ClearNeedsDraw () NeedsDrawRect = Rectangle.Empty; SubViewNeedsDraw = false; - if (Margin is { } && Margin.Thickness != Thickness.Empty) + if (Margin is { } && (Margin.Thickness != Thickness.Empty || Margin.SubViewNeedsDraw || Margin.NeedsDraw)) { Margin?.ClearNeedsDraw (); } - if (Border is { } && Border.Thickness != Thickness.Empty) + if (Border is { } && (Border.Thickness != Thickness.Empty || Border.SubViewNeedsDraw || Border.NeedsDraw)) { Border?.ClearNeedsDraw (); } - if (Padding is { } && Padding.Thickness != Thickness.Empty) + if (Padding is { } && (Padding.Thickness != Thickness.Empty || Padding.SubViewNeedsDraw || Padding.NeedsDraw)) { Padding?.ClearNeedsDraw (); } @@ -876,7 +906,6 @@ protected void ClearNeedsDraw () { LineCanvas.Clear (); } - } #endregion NeedsDraw diff --git a/Terminal.Gui/ViewBase/View.Layout.cs b/Terminal.Gui/ViewBase/View.Layout.cs index 41339fdf8a..a551ee2ecd 100644 --- a/Terminal.Gui/ViewBase/View.Layout.cs +++ b/Terminal.Gui/ViewBase/View.Layout.cs @@ -419,7 +419,10 @@ public bool Layout (Size contentSize) { LayoutSubViews (); - // Debug.Assert(!NeedsLayout); + // A layout was performed so a draw is needed + // NeedsLayout may still be true if a dependent View still needs layout after SubViewsLaidOut event + SetNeedsDraw (); + return true; } diff --git a/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs b/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs index 1a0839eb32..2f425ccd44 100644 --- a/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs +++ b/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs @@ -10,7 +10,7 @@ public void NeedsDraw_False_If_Width_Height_Zero () View view = new () { Width = 0, Height = 0 }; view.BeginInit (); view.EndInit (); - Assert.True (view.NeedsDraw); + Assert.False (view.NeedsDraw); //Assert.False (view.SubViewNeedsDraw); } @@ -70,14 +70,16 @@ public void NeedsDraw_True_After_BeginInit () view.NeedsDraw = false; view.BeginInit (); - Assert.True (view.NeedsDraw); // Because layout is still needed + Assert.False (view.NeedsDraw); // Because layout is still needed view.Layout (); - Assert.False (view.NeedsDraw); + // NeedsDraw is true after layout and NeedsLayout is false if SubViewsLaidOut doesn't call SetNeedsLayout + Assert.True (view.NeedsDraw); + Assert.False (view.NeedsLayout); } [Fact] - public void NeedsDraw_False_After_EndInit () + public void NeedsDraw_True_After_EndInit_Where_Call_Layout () { var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; Assert.True (view.NeedsDraw); @@ -96,7 +98,7 @@ public void NeedsDraw_False_After_EndInit () } [Fact] - public void NeedsDraw_After_SetLayoutNeeded () + public void NeedsDraw_After_SetLayoutNeeded_And_Layout () { var view = new View { Width = 2, Height = 2 }; Assert.True (view.NeedsDraw); @@ -107,8 +109,12 @@ public void NeedsDraw_After_SetLayoutNeeded () Assert.False (view.NeedsLayout); view.SetNeedsLayout (); - Assert.True (view.NeedsDraw); + Assert.False (view.NeedsDraw); Assert.True (view.NeedsLayout); + + view.Layout (); + Assert.True (view.NeedsDraw); + Assert.False (view.NeedsLayout); } [Fact] @@ -121,21 +127,27 @@ public void NeedsDraw_False_After_SetRelativeLayout_Absolute_Dims () Assert.False (view.NeedsDraw); Assert.False (view.NeedsLayout); - // SRL won't change anything since the view is Absolute + // SRL won't change anything since the view frame wasn't changed view.SetRelativeLayout (Application.Screen.Size); Assert.False (view.NeedsDraw); view.SetNeedsLayout (); - // SRL won't change anything since the view is Absolute + // SRL won't change anything since the view frame wasn't changed + // SRL doesn't depend on NeedsLayout, but LayoutSubViews does view.SetRelativeLayout (Application.Screen.Size); + Assert.False (view.NeedsDraw); + Assert.True (view.NeedsLayout); + + view.Layout (); Assert.True (view.NeedsDraw); + Assert.False (view.NeedsLayout); view.NeedsDraw = false; - // SRL won't change anything since the view is Absolute. However, Layout has not been called + // SRL won't change anything since the view frame wasn't changed. However, Layout has not been called view.SetRelativeLayout (new (10, 10)); - Assert.True (view.NeedsDraw); + Assert.False (view.NeedsDraw); } [Fact] @@ -149,17 +161,20 @@ public void NeedsDraw_False_After_SetRelativeLayout_Relative_Dims () Width = Dim.Fill (), Height = Dim.Fill () }; - Assert.True (superView.NeedsDraw); + + // A layout wasn't called yet, so NeedsDraw is still empty + Assert.False (superView.NeedsDraw); superView.Add (view); - Assert.True (view.NeedsDraw); - Assert.True (superView.NeedsDraw); + // A layout wasn't called yet, so NeedsDraw is still empty + Assert.False (view.NeedsDraw); + Assert.False (superView.NeedsDraw); superView.BeginInit (); - Assert.True (view.NeedsDraw); - Assert.True (superView.NeedsDraw); + Assert.False (view.NeedsDraw); + Assert.False (superView.NeedsDraw); - superView.EndInit (); + superView.EndInit (); // Call Layout Assert.True (view.NeedsDraw); Assert.True (superView.NeedsDraw); @@ -177,9 +192,10 @@ public void NeedsDraw_False_After_SetRelativeLayout_10x10 () Width = Dim.Fill (), Height = Dim.Fill () }; - Assert.True (superView.NeedsDraw); + Assert.False (superView.NeedsDraw); superView.Layout (); + Assert.True (superView.NeedsDraw); superView.NeedsDraw = false; superView.SetRelativeLayout (new (10, 10)); From 03120b93fb496acae8e84ab30f0304bfcb0137a8 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 13:19:27 +0100 Subject: [PATCH 10/34] Add assertion proving NeedsLayout is always false before call OnSubViewsLaidOut --- Terminal.Gui/ViewBase/View.Layout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ViewBase/View.Layout.cs b/Terminal.Gui/ViewBase/View.Layout.cs index a551ee2ecd..16725ebfd0 100644 --- a/Terminal.Gui/ViewBase/View.Layout.cs +++ b/Terminal.Gui/ViewBase/View.Layout.cs @@ -703,7 +703,7 @@ protected virtual void OnSubViewLayout (LayoutEventArgs args) { } /// Override to perform tasks after the has been resized or the layout has /// otherwise changed. /// - protected virtual void OnSubViewsLaidOut (LayoutEventArgs args) { } + protected virtual void OnSubViewsLaidOut (LayoutEventArgs args) { Debug.Assert (!NeedsLayout); } /// Raised after all sub-views have been laid out. /// From 58fd81e521d6e92d1000e2cbfdda654547d2db40 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 13:37:23 +0100 Subject: [PATCH 11/34] Fix unit test error --- Terminal.Gui/Views/Menuv1/Menu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Menuv1/Menu.cs b/Terminal.Gui/Views/Menuv1/Menu.cs index 8969595acd..c4579aab35 100644 --- a/Terminal.Gui/Views/Menuv1/Menu.cs +++ b/Terminal.Gui/Views/Menuv1/Menu.cs @@ -527,7 +527,7 @@ private void Application_RootMouseEvent (object? sender, MouseEventArgs a) private void Application_UnGrabbedMouse (object? sender, ViewEventArgs a) { - if (_host.IsMenuOpen) + if (_host is { IsMenuOpen: true }) { _host.CloseAllMenus (); } From d7306e72f3169f5c289028f135790205931816ab Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 15:00:23 +0100 Subject: [PATCH 12/34] Increasing time to abort --- Tests/IntegrationTests/UICatalog/ScenarioTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/IntegrationTests/UICatalog/ScenarioTests.cs b/Tests/IntegrationTests/UICatalog/ScenarioTests.cs index ebb6062135..a12285e55b 100644 --- a/Tests/IntegrationTests/UICatalog/ScenarioTests.cs +++ b/Tests/IntegrationTests/UICatalog/ScenarioTests.cs @@ -42,7 +42,7 @@ public void All_Scenarios_Quit_And_Init_Shutdown_Properly (Type scenarioType) _output.WriteLine ($"Running Scenario '{scenarioType}'"); var scenario = Activator.CreateInstance (scenarioType) as Scenario; - uint abortTime = 2000; + uint abortTime = 3000; // Some scenarios may take a while to quit, so we give them 3 seconds object? timeout = null; var initialized = false; var shutdownGracefully = false; From 64e0bb57876cc3027e88f16717b81d9ae5ece137 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 15:12:49 +0100 Subject: [PATCH 13/34] Revert "Increasing time to abort" This reverts commit d7306e72f3169f5c289028f135790205931816ab. --- Tests/IntegrationTests/UICatalog/ScenarioTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/IntegrationTests/UICatalog/ScenarioTests.cs b/Tests/IntegrationTests/UICatalog/ScenarioTests.cs index a12285e55b..ebb6062135 100644 --- a/Tests/IntegrationTests/UICatalog/ScenarioTests.cs +++ b/Tests/IntegrationTests/UICatalog/ScenarioTests.cs @@ -42,7 +42,7 @@ public void All_Scenarios_Quit_And_Init_Shutdown_Properly (Type scenarioType) _output.WriteLine ($"Running Scenario '{scenarioType}'"); var scenario = Activator.CreateInstance (scenarioType) as Scenario; - uint abortTime = 3000; // Some scenarios may take a while to quit, so we give them 3 seconds + uint abortTime = 2000; object? timeout = null; var initialized = false; var shutdownGracefully = false; From f25bcd8e4a09d5aa1d8f4aa2d04abb6f709b568a Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 16:13:41 +0100 Subject: [PATCH 14/34] Trying fix integration tests --- Terminal.Gui/ViewBase/View.Drawing.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index 84f40c337b..12ffcf1e3b 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -836,8 +836,10 @@ public void SetNeedsDraw (Rectangle viewPortRelativeRegion) adornment.Parent?.SetSubViewNeedsDraw (); } - // There was multiple enumeration error here, so calling ToArray - probably a stop gap - foreach (View subview in InternalSubViews.ToArray ()) + // There was multiple enumeration error here, so calling new collection - probably a stop gap + List subviews = new (InternalSubViews); + + foreach (View subview in subviews) { if (subview.Frame.IntersectsWith (viewPortRelativeRegion)) { From c6774b2825cd3d6eab023078aa0116e8377fd000 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 17:24:03 +0100 Subject: [PATCH 15/34] Still trying fix integrations unit tests --- Terminal.Gui/ViewBase/Layout/DimAuto.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ViewBase/Layout/DimAuto.cs b/Terminal.Gui/ViewBase/Layout/DimAuto.cs index 6974fd45b2..7410d49928 100644 --- a/Terminal.Gui/ViewBase/Layout/DimAuto.cs +++ b/Terminal.Gui/ViewBase/Layout/DimAuto.cs @@ -48,7 +48,7 @@ internal override int Calculate (int location, int superviewContentSize, View us us.TextFormatter.ConstrainToSize = us.TextFormatter.FormatAndGetSize (new (int.Min (autoMax, screenX4), screenX4)); } - textSize = us.TextFormatter.ConstrainToWidth!.Value; + textSize = us.TextFormatter.ConstrainToWidth ?? 0; } else { From 62bd31c8c2035bddf07ca17bf3cb16a4401dc5fb Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 17:43:40 +0100 Subject: [PATCH 16/34] Revert comment --- Terminal.Gui/App/Application.Run.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index 26aa447724..32127a49cf 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -189,7 +189,7 @@ public static RunState Begin (Toplevel toplevel) if (!toplevel.IsInitialized) { toplevel.BeginInit (); - toplevel.EndInit (); // Calls SetNeedsLayout + toplevel.EndInit (); // Calls Layout } // Call ConfigurationManager Apply here to ensure all subscribers to ConfigurationManager.Applied From 93292580698db62acc3ff9bf205a0e5920a892ba Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 17:45:48 +0100 Subject: [PATCH 17/34] Layout is performed during the iteration --- Terminal.Gui/ViewBase/Layout/DimFunc.cs | 7 +------ Terminal.Gui/ViewBase/Layout/PosFunc.cs | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Terminal.Gui/ViewBase/Layout/DimFunc.cs b/Terminal.Gui/ViewBase/Layout/DimFunc.cs index 1ba061f926..0773dc316f 100644 --- a/Terminal.Gui/ViewBase/Layout/DimFunc.cs +++ b/Terminal.Gui/ViewBase/Layout/DimFunc.cs @@ -26,10 +26,5 @@ public record DimFunc (Func Fn, View? View = null) : Dim /// public override string ToString () { return $"DimFunc({Fn (View)})"; } - internal override int GetAnchor (int size) - { - View?.Layout (); - - return Fn (View); - } + internal override int GetAnchor (int size) { return Fn (View); } } diff --git a/Terminal.Gui/ViewBase/Layout/PosFunc.cs b/Terminal.Gui/ViewBase/Layout/PosFunc.cs index a3eb5f5891..3900beb463 100644 --- a/Terminal.Gui/ViewBase/Layout/PosFunc.cs +++ b/Terminal.Gui/ViewBase/Layout/PosFunc.cs @@ -25,10 +25,5 @@ public record PosFunc (Func Fn, View? View = null) : Pos /// public override string ToString () { return $"PosFunc({Fn (View)})"; } - internal override int GetAnchor (int size) - { - View?.Layout (); - - return Fn (View); - } + internal override int GetAnchor (int size) { return Fn (View); } } \ No newline at end of file From 566b5152b5cf04bdb51f6a75d0610870f78cee90 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 17:47:45 +0100 Subject: [PATCH 18/34] Using Dim.Func with status bar view --- Examples/UICatalog/UICatalogTop.cs | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/Examples/UICatalog/UICatalogTop.cs b/Examples/UICatalog/UICatalogTop.cs index f5a8213f88..7253d695e8 100644 --- a/Examples/UICatalog/UICatalogTop.cs +++ b/Examples/UICatalog/UICatalogTop.cs @@ -404,20 +404,7 @@ private TableView CreateScenarioList () X = Pos.Right (_categoryList!) - 1, Y = Pos.Bottom (_menuBar!), Width = Dim.Fill (), - Height = Dim.Fill ( - Dim.Func ( - _ => - { - if (_statusBar!.NeedsLayout) - { - throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout."); - - //_statusBar.Layout (); - } - - return _statusBar.Frame.Height; - })), - + Height = Dim.Fill (Dim.Func (v => v!.Frame.Height, _statusBar)), //AllowsMarking = false, CanFocus = true, Title = "_Scenarios", @@ -515,19 +502,7 @@ private ListView CreateCategoryList () X = 0, Y = Pos.Bottom (_menuBar!), Width = Dim.Auto (), - Height = Dim.Fill ( - Dim.Func ( - _ => - { - if (_statusBar!.NeedsLayout) - { - throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout."); - - //_statusBar.Layout (); - } - - return _statusBar.Frame.Height; - })), + Height = Dim.Fill (Dim.Func (v => v!.Frame.Height, _statusBar)), AllowsMarking = false, CanFocus = true, Title = "_Categories", From ae75832e002bec711f1bed32443bb25220c5ad92 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 20:05:49 +0100 Subject: [PATCH 19/34] Still trying fix integrations tests by locking _subviews --- Terminal.Gui/ViewBase/View.Drawing.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index 12ffcf1e3b..7562e122c0 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -837,7 +837,12 @@ public void SetNeedsDraw (Rectangle viewPortRelativeRegion) } // There was multiple enumeration error here, so calling new collection - probably a stop gap - List subviews = new (InternalSubViews); + List subviews; + + lock (_subviews!) + { + subviews = new (InternalSubViews); + } foreach (View subview in subviews) { From fc354860fdc6a34888c43b6d584f952e3159e485 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 20:05:49 +0100 Subject: [PATCH 20/34] Still trying fix integrations tests by locking _subviews --- Terminal.Gui/ViewBase/View.Drawing.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index 12ffcf1e3b..7562e122c0 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -837,7 +837,12 @@ public void SetNeedsDraw (Rectangle viewPortRelativeRegion) } // There was multiple enumeration error here, so calling new collection - probably a stop gap - List subviews = new (InternalSubViews); + List subviews; + + lock (_subviews!) + { + subviews = new (InternalSubViews); + } foreach (View subview in subviews) { From a892bff42be642494782771546d073e357e7585b Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 22:38:57 +0100 Subject: [PATCH 21/34] Add internal SnapshotSubviews method --- Terminal.Gui/ViewBase/Layout/DimAuto.cs | 2 +- Terminal.Gui/ViewBase/View.Drawing.cs | 16 +++++++--------- Terminal.Gui/ViewBase/View.Hierarchy.cs | 8 ++++++++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Terminal.Gui/ViewBase/Layout/DimAuto.cs b/Terminal.Gui/ViewBase/Layout/DimAuto.cs index 7410d49928..6c21cdf41e 100644 --- a/Terminal.Gui/ViewBase/Layout/DimAuto.cs +++ b/Terminal.Gui/ViewBase/Layout/DimAuto.cs @@ -81,7 +81,7 @@ internal override int Calculate (int location, int superviewContentSize, View us { // TOOD: All the below is a naive implementation. It may be possible to optimize this. - List includedSubViews = us.InternalSubViews.ToList (); + List includedSubViews = us.SnapshotSubviews (); // If [x] it can cause `us.ContentSize` to change. // If [ ] it doesn't need special processing for us to determine `us.ContentSize`. diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index 7562e122c0..88ce777917 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -836,15 +836,10 @@ public void SetNeedsDraw (Rectangle viewPortRelativeRegion) adornment.Parent?.SetSubViewNeedsDraw (); } - // There was multiple enumeration error here, so calling new collection - probably a stop gap - List subviews; + // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap + List snapshot = SnapshotSubviews (); - lock (_subviews!) - { - subviews = new (InternalSubViews); - } - - foreach (View subview in subviews) + foreach (View subview in snapshot) { if (subview.Frame.IntersectsWith (viewPortRelativeRegion)) { @@ -898,7 +893,10 @@ protected void ClearNeedsDraw () Padding?.ClearNeedsDraw (); } - foreach (View subview in SubViews) + // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap + List snapshot = SnapshotSubviews (); + + foreach (View subview in snapshot) { subview.ClearNeedsDraw (); } diff --git a/Terminal.Gui/ViewBase/View.Hierarchy.cs b/Terminal.Gui/ViewBase/View.Hierarchy.cs index d7510c2b07..b22e92a126 100644 --- a/Terminal.Gui/ViewBase/View.Hierarchy.cs +++ b/Terminal.Gui/ViewBase/View.Hierarchy.cs @@ -36,6 +36,14 @@ public View? SuperView private set => SetSuperView (value); } + internal List SnapshotSubviews () + { + lock (InternalSubViews) + { + return [..InternalSubViews]; + } + } + private void SetSuperView (View? value) { if (_superView == value) From c67f93a3ec18f986d97959e73b1b39ea7e39a97b Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 23:18:52 +0100 Subject: [PATCH 22/34] Remove lock from SnapshotSubviews method --- Terminal.Gui/ViewBase/View.Drawing.cs | 8 ++------ Terminal.Gui/ViewBase/View.Hierarchy.cs | 8 +------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index 88ce777917..57ad577383 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -837,9 +837,7 @@ public void SetNeedsDraw (Rectangle viewPortRelativeRegion) } // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap - List snapshot = SnapshotSubviews (); - - foreach (View subview in snapshot) + foreach (View subview in SnapshotSubviews ()) { if (subview.Frame.IntersectsWith (viewPortRelativeRegion)) { @@ -894,9 +892,7 @@ protected void ClearNeedsDraw () } // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap - List snapshot = SnapshotSubviews (); - - foreach (View subview in snapshot) + foreach (View subview in SnapshotSubviews ()) { subview.ClearNeedsDraw (); } diff --git a/Terminal.Gui/ViewBase/View.Hierarchy.cs b/Terminal.Gui/ViewBase/View.Hierarchy.cs index b22e92a126..57de4ffb93 100644 --- a/Terminal.Gui/ViewBase/View.Hierarchy.cs +++ b/Terminal.Gui/ViewBase/View.Hierarchy.cs @@ -36,13 +36,7 @@ public View? SuperView private set => SetSuperView (value); } - internal List SnapshotSubviews () - { - lock (InternalSubViews) - { - return [..InternalSubViews]; - } - } + internal List SnapshotSubviews () { return [..InternalSubViews]; } private void SetSuperView (View? value) { From 83c00f84d07e0d6da3e872d4c0204d4eed9e5318 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 23:46:12 +0100 Subject: [PATCH 23/34] Using SnapshotSubviews method in the DrawSubViews method --- Terminal.Gui/ViewBase/View.Drawing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index 57ad577383..22910b5dec 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -580,7 +580,7 @@ public void DrawSubViews (DrawContext? context = null) } // Draw the subviews in reverse order to leverage clipping. - foreach (View view in InternalSubViews.Where (view => view.Visible).Reverse ()) + foreach (View view in SnapshotSubviews().Where (view => view.Visible).Reverse ()) { // TODO: HACK - This forcing of SetNeedsDraw with SuperViewRendersLineCanvas enables auto line join to work, but is brute force. if (view.SuperViewRendersLineCanvas || view.ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent)) From 08123da47a3b6cbfd564cf8073a369e1c519aa58 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 23:18:52 +0100 Subject: [PATCH 24/34] Remove lock from SnapshotSubviews method --- Terminal.Gui/ViewBase/View.Drawing.cs | 8 ++------ Terminal.Gui/ViewBase/View.Hierarchy.cs | 8 +------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index 88ce777917..57ad577383 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -837,9 +837,7 @@ public void SetNeedsDraw (Rectangle viewPortRelativeRegion) } // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap - List snapshot = SnapshotSubviews (); - - foreach (View subview in snapshot) + foreach (View subview in SnapshotSubviews ()) { if (subview.Frame.IntersectsWith (viewPortRelativeRegion)) { @@ -894,9 +892,7 @@ protected void ClearNeedsDraw () } // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap - List snapshot = SnapshotSubviews (); - - foreach (View subview in snapshot) + foreach (View subview in SnapshotSubviews ()) { subview.ClearNeedsDraw (); } diff --git a/Terminal.Gui/ViewBase/View.Hierarchy.cs b/Terminal.Gui/ViewBase/View.Hierarchy.cs index b22e92a126..57de4ffb93 100644 --- a/Terminal.Gui/ViewBase/View.Hierarchy.cs +++ b/Terminal.Gui/ViewBase/View.Hierarchy.cs @@ -36,13 +36,7 @@ public View? SuperView private set => SetSuperView (value); } - internal List SnapshotSubviews () - { - lock (InternalSubViews) - { - return [..InternalSubViews]; - } - } + internal List SnapshotSubviews () { return [..InternalSubViews]; } private void SetSuperView (View? value) { From 3dcb182f346382ef64a6e0876312789ab5146af6 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Jul 2025 23:46:12 +0100 Subject: [PATCH 25/34] Using SnapshotSubviews method in the DrawSubViews method --- Terminal.Gui/ViewBase/View.Drawing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index 57ad577383..22910b5dec 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -580,7 +580,7 @@ public void DrawSubViews (DrawContext? context = null) } // Draw the subviews in reverse order to leverage clipping. - foreach (View view in InternalSubViews.Where (view => view.Visible).Reverse ()) + foreach (View view in SnapshotSubviews().Where (view => view.Visible).Reverse ()) { // TODO: HACK - This forcing of SetNeedsDraw with SuperViewRendersLineCanvas enables auto line join to work, but is brute force. if (view.SuperViewRendersLineCanvas || view.ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent)) From b7dfca6c8b1ab2d395e3e3a8ac93202594e56c2e Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 29 Jul 2025 12:51:16 +0100 Subject: [PATCH 26/34] Using SnapshotSubviews --- Terminal.Gui/ViewBase/Layout/PosAlign.cs | 2 +- Terminal.Gui/ViewBase/View.Layout.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ViewBase/Layout/PosAlign.cs b/Terminal.Gui/ViewBase/Layout/PosAlign.cs index 82187c5730..b85a9c7215 100644 --- a/Terminal.Gui/ViewBase/Layout/PosAlign.cs +++ b/Terminal.Gui/ViewBase/Layout/PosAlign.cs @@ -117,7 +117,7 @@ internal override int Calculate (int superviewDimension, Dim dim, View us, Dimen } else { - groupViews = us.SuperView!.SubViews.Where (v => HasGroupId (v, dimension, GroupId)).ToList (); + groupViews = us.SuperView!.SnapshotSubviews ().Where (v => HasGroupId (v, dimension, GroupId)).ToList (); } AlignAndUpdateGroup (GroupId, groupViews, dimension, superviewDimension); diff --git a/Terminal.Gui/ViewBase/View.Layout.cs b/Terminal.Gui/ViewBase/View.Layout.cs index 16725ebfd0..65308cc099 100644 --- a/Terminal.Gui/ViewBase/View.Layout.cs +++ b/Terminal.Gui/ViewBase/View.Layout.cs @@ -764,7 +764,7 @@ public void SetNeedsLayout () // TODO: Optimize this - see Setting_Thickness_Causes_Adornment_SubView_Layout // Use a stack to avoid recursion - Stack stack = new (SubViews); + Stack stack = new (SnapshotSubviews ()); while (stack.Count > 0) { From ca52522791a8ff75d14a0bc8d9208697f039529d Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 29 Jul 2025 12:53:41 +0100 Subject: [PATCH 27/34] Prevent new app if the previous wasn't yet finished --- .../GuiTestContext.cs | 92 +++++++++++-------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs index 6321a86cbc..933782c668 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs @@ -46,45 +46,65 @@ internal GuiTestContext (Func topLevelBuilder, int width, int height, lock (_threadLock) { // Start the application in a background thread - _runTask = Task.Run ( - () => + _runTask = Task.Run (() => { - try + while (Application.Top is { }) { - ApplicationImpl.ChangeInstance (v2); - - ILogger logger = LoggerFactory.Create ( - builder => - builder.SetMinimumLevel (LogLevel.Trace) - .AddProvider (new TextWriterLoggerProvider (new StringWriter (_logsSb)))) - .CreateLogger ("Test Logging"); - Logging.Logger = logger; - - v2.Init (null, GetDriverName ()); - - booting.Release (); - - Toplevel t = topLevelBuilder (); - t.Closed += (s, e) => { _finished = true; }; - Application.Run (t); // This will block, but it's on a background thread now - - t.Dispose (); - Application.Shutdown (); - } - catch (OperationCanceledException) - { } - catch (Exception ex) - { - _ex = ex; - } - finally - { - ApplicationImpl.ChangeInstance (origApp); - Logging.Logger = origLogger; - _finished = true; + Task.Delay (300).Wait (); } - }, - _cts.Token); + }) + .ContinueWith ( + (task, _) => + { + try + { + if (task.IsFaulted) + { + _ex = task.Exception ?? new Exception ("Unknown error in background task"); + } + + // Ensure we are not running on the main thread + if (ApplicationImpl.Instance != origApp) + { + throw new InvalidOperationException ( + "Application instance is not the original one, this should not happen."); + } + + ApplicationImpl.ChangeInstance (v2); + + ILogger logger = LoggerFactory.Create (builder => + builder.SetMinimumLevel (LogLevel.Trace) + .AddProvider ( + new TextWriterLoggerProvider ( + new StringWriter (_logsSb)))) + .CreateLogger ("Test Logging"); + Logging.Logger = logger; + + v2.Init (null, GetDriverName ()); + + booting.Release (); + + Toplevel t = topLevelBuilder (); + t.Closed += (s, e) => { _finished = true; }; + Application.Run (t); // This will block, but it's on a background thread now + + t.Dispose (); + Application.Shutdown (); + } + catch (OperationCanceledException) + { } + catch (Exception ex) + { + _ex = ex; + } + finally + { + ApplicationImpl.ChangeInstance (origApp); + Logging.Logger = origLogger; + _finished = true; + } + }, + _cts.Token); } // Wait for booting to complete with a timeout to avoid hangs From 732bff4d2a23d54d756c0ceb52459677818477e5 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 29 Jul 2025 20:01:03 +0100 Subject: [PATCH 28/34] Replace SnapshotSubviews method with ViewCollectionHelpers class --- Terminal.Gui/ViewBase/Layout/DimAuto.cs | 2 +- Terminal.Gui/ViewBase/Layout/PosAlign.cs | 2 +- Terminal.Gui/ViewBase/View.Drawing.cs | 9 +++++---- Terminal.Gui/ViewBase/View.Hierarchy.cs | 21 +++++++++++++++++++-- Terminal.Gui/ViewBase/View.Layout.cs | 4 +++- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/ViewBase/Layout/DimAuto.cs b/Terminal.Gui/ViewBase/Layout/DimAuto.cs index 6c21cdf41e..34ad05b197 100644 --- a/Terminal.Gui/ViewBase/Layout/DimAuto.cs +++ b/Terminal.Gui/ViewBase/Layout/DimAuto.cs @@ -81,7 +81,7 @@ internal override int Calculate (int location, int superviewContentSize, View us { // TOOD: All the below is a naive implementation. It may be possible to optimize this. - List includedSubViews = us.SnapshotSubviews (); + List includedSubViews = us.SubViews.Snapshot ().ToList (); // If [x] it can cause `us.ContentSize` to change. // If [ ] it doesn't need special processing for us to determine `us.ContentSize`. diff --git a/Terminal.Gui/ViewBase/Layout/PosAlign.cs b/Terminal.Gui/ViewBase/Layout/PosAlign.cs index b85a9c7215..4d72cc9afb 100644 --- a/Terminal.Gui/ViewBase/Layout/PosAlign.cs +++ b/Terminal.Gui/ViewBase/Layout/PosAlign.cs @@ -117,7 +117,7 @@ internal override int Calculate (int superviewDimension, Dim dim, View us, Dimen } else { - groupViews = us.SuperView!.SnapshotSubviews ().Where (v => HasGroupId (v, dimension, GroupId)).ToList (); + groupViews = us.SuperView!.SubViews.Snapshot ().Where (v => HasGroupId (v, dimension, GroupId)).ToList (); } AlignAndUpdateGroup (GroupId, groupViews, dimension, superviewDimension); diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index 22910b5dec..8996797abb 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -13,7 +13,8 @@ public partial class View // Drawing APIs /// If , will be called on each view to force it to be drawn. internal static void Draw (IEnumerable views, bool force) { - IEnumerable viewsArray = views as View [] ?? views.ToArray (); + // **Snapshot once** — every recursion level gets its own frozen array + View [] viewsArray = views.Snapshot (); // The draw context is used to track the region drawn by each view. DrawContext context = new DrawContext (); @@ -580,7 +581,7 @@ public void DrawSubViews (DrawContext? context = null) } // Draw the subviews in reverse order to leverage clipping. - foreach (View view in SnapshotSubviews().Where (view => view.Visible).Reverse ()) + foreach (View view in InternalSubViews.Snapshot ().Where (v => v.Visible).Reverse ()) { // TODO: HACK - This forcing of SetNeedsDraw with SuperViewRendersLineCanvas enables auto line join to work, but is brute force. if (view.SuperViewRendersLineCanvas || view.ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent)) @@ -837,7 +838,7 @@ public void SetNeedsDraw (Rectangle viewPortRelativeRegion) } // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap - foreach (View subview in SnapshotSubviews ()) + foreach (View subview in InternalSubViews.Snapshot ()) { if (subview.Frame.IntersectsWith (viewPortRelativeRegion)) { @@ -892,7 +893,7 @@ protected void ClearNeedsDraw () } // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap - foreach (View subview in SnapshotSubviews ()) + foreach (View subview in InternalSubViews.Snapshot ()) { subview.ClearNeedsDraw (); } diff --git a/Terminal.Gui/ViewBase/View.Hierarchy.cs b/Terminal.Gui/ViewBase/View.Hierarchy.cs index 57de4ffb93..2d86b035ad 100644 --- a/Terminal.Gui/ViewBase/View.Hierarchy.cs +++ b/Terminal.Gui/ViewBase/View.Hierarchy.cs @@ -36,8 +36,6 @@ public View? SuperView private set => SetSuperView (value); } - internal List SnapshotSubviews () { return [..InternalSubViews]; } - private void SetSuperView (View? value) { if (_superView == value) @@ -560,3 +558,22 @@ private void PerformActionForSubView (View subview, Action action) #endregion SubViewOrdering } + +internal static class ViewCollectionHelpers +{ + /// Returns a defensive copy of any . + internal static View [] Snapshot (this IEnumerable source) + { + if (source is IList list) + { + // The list parameter might be the live `_subviews`, so freeze it under a lock + lock (list) + { + return [.. list]; // C# 12 slice copy (= new List(list).ToArray()) + } + } + + // Anything else (LINQ result, iterator block, etc.) we just enumerate. + return source.ToArray (); // Safe because it’s not shared mutable state + } +} diff --git a/Terminal.Gui/ViewBase/View.Layout.cs b/Terminal.Gui/ViewBase/View.Layout.cs index 65308cc099..746781014d 100644 --- a/Terminal.Gui/ViewBase/View.Layout.cs +++ b/Terminal.Gui/ViewBase/View.Layout.cs @@ -764,10 +764,12 @@ public void SetNeedsLayout () // TODO: Optimize this - see Setting_Thickness_Causes_Adornment_SubView_Layout // Use a stack to avoid recursion - Stack stack = new (SnapshotSubviews ()); + Stack stack = new (InternalSubViews.Snapshot ().ToList ()); while (stack.Count > 0) { + Debug.Assert (stack.Peek () is { }); + View current = stack.Pop (); if (!current.NeedsLayout) From 146a290b541237ad165fa0e7e0dd8d5248b940dc Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 29 Jul 2025 20:04:30 +0100 Subject: [PATCH 29/34] Lock entire GuiTestContext constructor --- .../GuiTestContext.cs | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs index 933782c668..37b5577256 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs @@ -25,26 +25,26 @@ public class GuiTestContext : IDisposable internal GuiTestContext (Func topLevelBuilder, int width, int height, V2TestDriver driver) { - IApplication origApp = ApplicationImpl.Instance; - ILogger? origLogger = Logging.Logger; - _logsSb = new (); - _driver = driver; + lock (_threadLock) + { + IApplication origApp = ApplicationImpl.Instance; + ILogger? origLogger = Logging.Logger; + _logsSb = new (); + _driver = driver; - _netInput = new (_cts.Token); - _winInput = new (_cts.Token); + _netInput = new (_cts.Token); + _winInput = new (_cts.Token); - _output.Size = new (width, height); + _output.Size = new (width, height); - var v2 = new ApplicationV2 ( - () => _netInput, - () => _output, - () => _winInput, - () => _output); + var v2 = new ApplicationV2 ( + () => _netInput, + () => _output, + () => _winInput, + () => _output); - var booting = new SemaphoreSlim (0, 1); + var booting = new SemaphoreSlim (0, 1); - lock (_threadLock) - { // Start the application in a background thread _runTask = Task.Run (() => { @@ -75,8 +75,8 @@ internal GuiTestContext (Func topLevelBuilder, int width, int height, ILogger logger = LoggerFactory.Create (builder => builder.SetMinimumLevel (LogLevel.Trace) .AddProvider ( - new TextWriterLoggerProvider ( - new StringWriter (_logsSb)))) + new TextWriterLoggerProvider ( + new StringWriter (_logsSb)))) .CreateLogger ("Test Logging"); Logging.Logger = logger; @@ -105,15 +105,15 @@ internal GuiTestContext (Func topLevelBuilder, int width, int height, } }, _cts.Token); - } - // Wait for booting to complete with a timeout to avoid hangs - if (!booting.WaitAsync (TimeSpan.FromSeconds (10)).Result) - { - throw new TimeoutException ("Application failed to start within the allotted time."); - } + // Wait for booting to complete with a timeout to avoid hangs + if (!booting.WaitAsync (TimeSpan.FromSeconds (10)).Result) + { + throw new TimeoutException ("Application failed to start within the allotted time."); + } - WaitIteration (); + WaitIteration (); + } } private string GetDriverName () From 1d40f805844d09b8655ce2572f44342e6cbdfdb9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 29 Jul 2025 21:28:39 +0100 Subject: [PATCH 30/34] Using Snapshot in the ordered field --- Terminal.Gui/ViewBase/View.Layout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ViewBase/View.Layout.cs b/Terminal.Gui/ViewBase/View.Layout.cs index 746781014d..c3c4a407be 100644 --- a/Terminal.Gui/ViewBase/View.Layout.cs +++ b/Terminal.Gui/ViewBase/View.Layout.cs @@ -638,7 +638,7 @@ internal void LayoutSubViews () List redo = new (); - foreach (View v in ordered) + foreach (View v in ordered.Snapshot ()) { if (!v.Layout (contentSize)) { From 2d4df3715101f3b0cc2759a459dcd8d87b8237a4 Mon Sep 17 00:00:00 2001 From: Thomas Nind <31306100+tznind@users.noreply.github.com> Date: Sun, 31 Aug 2025 16:57:30 +0100 Subject: [PATCH 31/34] Fixes #4221 Extra modifiers f1 to f4 in v2net (#4220) * Assume we are running in a terminal that supports true color by default unless user explicitly forces 16 * Add support for extra modifiers for F1 to F4 keys * Revert "Assume we are running in a terminal that supports true color by default unless user explicitly forces 16" This reverts commit 4cc2530de04c79554c0747ba4a2ca877c46058b8. * Cleanup * Update comments * Code cleanup --------- Co-authored-by: Tig --- .../AnsiResponseParser/Keyboard/CsiCursorPattern.cs | 12 +++++++++--- .../ConsoleDrivers/AnsiKeyboardParserTests.cs | 3 +-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Drivers/AnsiResponseParser/Keyboard/CsiCursorPattern.cs b/Terminal.Gui/Drivers/AnsiResponseParser/Keyboard/CsiCursorPattern.cs index 0255e7394c..744658a767 100644 --- a/Terminal.Gui/Drivers/AnsiResponseParser/Keyboard/CsiCursorPattern.cs +++ b/Terminal.Gui/Drivers/AnsiResponseParser/Keyboard/CsiCursorPattern.cs @@ -7,11 +7,11 @@ namespace Terminal.Gui.Drivers; /// Detects ansi escape sequences in strings that have been read from /// the terminal (see ). /// Handles navigation CSI key parsing such as \x1b[A (Cursor up) -/// and \x1b[1;5A (Cursor up with Ctrl) +/// and \x1b[1;5A (Cursor/Function with modifier(s)) /// public class CsiCursorPattern : AnsiKeyboardParserPattern { - private readonly Regex _pattern = new (@"^\u001b\[(?:1;(\d+))?([A-DHF])$"); + private readonly Regex _pattern = new (@"^\u001b\[(?:1;(\d+))?([A-DFHPQRS])$"); private readonly Dictionary _cursorMap = new () { @@ -20,7 +20,13 @@ public class CsiCursorPattern : AnsiKeyboardParserPattern { 'C', Key.CursorRight }, { 'D', Key.CursorLeft }, { 'H', Key.Home }, - { 'F', Key.End } + { 'F', Key.End }, + + // F1–F4 as per xterm VT100-style CSI sequences + { 'P', Key.F1 }, + { 'Q', Key.F2 }, + { 'R', Key.F3 }, + { 'S', Key.F4 } }; /// diff --git a/Tests/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs b/Tests/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs index d7bcedd18a..4e178bdc69 100644 --- a/Tests/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs +++ b/Tests/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs @@ -100,11 +100,10 @@ public class AnsiKeyboardParserTests yield return new object [] { "\u001b[24~", Key.F12 }; // Function keys with modifiers - /* Not currently supported yield return new object [] { "\u001b[1;2P", Key.F1.WithShift }; yield return new object [] { "\u001b[1;3Q", Key.F2.WithAlt }; yield return new object [] { "\u001b[1;5R", Key.F3.WithCtrl }; - */ + } // Consolidated test for all keyboard events (e.g., arrow keys) From 0e0337345b31c249e265d62aefc1a2724ee1c084 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 1 Sep 2025 00:21:30 +0100 Subject: [PATCH 32/34] Move ViewCollectionHelpers class to a separate file --- Terminal.Gui/ViewBase/View.Hierarchy.cs | 19 ------------------ .../ViewBase/ViewCollectionHelpers.cs | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 19 deletions(-) create mode 100644 Terminal.Gui/ViewBase/ViewCollectionHelpers.cs diff --git a/Terminal.Gui/ViewBase/View.Hierarchy.cs b/Terminal.Gui/ViewBase/View.Hierarchy.cs index 2d86b035ad..d7510c2b07 100644 --- a/Terminal.Gui/ViewBase/View.Hierarchy.cs +++ b/Terminal.Gui/ViewBase/View.Hierarchy.cs @@ -558,22 +558,3 @@ private void PerformActionForSubView (View subview, Action action) #endregion SubViewOrdering } - -internal static class ViewCollectionHelpers -{ - /// Returns a defensive copy of any . - internal static View [] Snapshot (this IEnumerable source) - { - if (source is IList list) - { - // The list parameter might be the live `_subviews`, so freeze it under a lock - lock (list) - { - return [.. list]; // C# 12 slice copy (= new List(list).ToArray()) - } - } - - // Anything else (LINQ result, iterator block, etc.) we just enumerate. - return source.ToArray (); // Safe because it’s not shared mutable state - } -} diff --git a/Terminal.Gui/ViewBase/ViewCollectionHelpers.cs b/Terminal.Gui/ViewBase/ViewCollectionHelpers.cs new file mode 100644 index 0000000000..1c7697d0c1 --- /dev/null +++ b/Terminal.Gui/ViewBase/ViewCollectionHelpers.cs @@ -0,0 +1,20 @@ +namespace Terminal.Gui.ViewBase; + +internal static class ViewCollectionHelpers +{ + /// Returns a defensive copy of any . + internal static View [] Snapshot (this IEnumerable source) + { + if (source is IList list) + { + // The list parameter might be the live `_subviews`, so freeze it under a lock + lock (list) + { + return [.. list]; // C# 12 slice copy (= new List(list).ToArray()) + } + } + + // Anything else (LINQ result, iterator block, etc.) we just enumerate. + return source.ToArray (); // Safe because it’s not shared mutable state + } +} From 552ab14fc8f4cbb9ecf3f3258841afbe99a1fc0d Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 1 Sep 2025 00:32:20 +0100 Subject: [PATCH 33/34] Remove Border.Layout call in the DoDrawAdornmentsSubViews method. --- Terminal.Gui/ViewBase/View.Drawing.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index 8996797abb..f4b6e86d0d 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -153,11 +153,6 @@ public void Draw (DrawContext? context = null) private void DoDrawAdornmentsSubViews () { - if (Border?.NeedsLayout == true) - { - Border.Layout (); - } - // NOTE: We do not support subviews of Margin? if (Border?.SubViews is { } && Border.Thickness != Thickness.Empty) From dd7c73fb2409cd1016be31827733270ce876fc26 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 1 Sep 2025 15:57:18 +0100 Subject: [PATCH 34/34] Remove adornments layout call within the draw --- Terminal.Gui/ViewBase/View.Drawing.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index f4b6e86d0d..ff90c27312 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -173,11 +173,6 @@ private void DoDrawAdornmentsSubViews () SetClip (saved); } - if (Padding?.NeedsLayout == true) - { - Padding.Layout (); - } - if (Padding?.SubViews is { } && Padding.Thickness != Thickness.Empty) { foreach (View subview in Padding.SubViews)