Sfoglia il codice sorgente

Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop

Tig 1 settimana fa
parent
commit
6ca870d14b
77 ha cambiato i file con 1146 aggiunte e 705 eliminazioni
  1. 1 2
      Directory.Packages.props
  2. 1 1
      Examples/UICatalog/Scenarios/Adornments.cs
  3. 4 4
      Examples/UICatalog/Scenarios/AllViewsTester.cs
  4. 1 1
      Examples/UICatalog/Scenarios/Arrangement.cs
  5. 2 1
      Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs
  6. 1 1
      Examples/UICatalog/Scenarios/ConfigurationEditor.cs
  7. 1 1
      Examples/UICatalog/Scenarios/DynamicMenuBar.cs
  8. 1 1
      Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentEditor.cs
  9. 1 1
      Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentsEditor.cs
  10. 3 3
      Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs
  11. 1 1
      Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs
  12. 3 3
      Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs
  13. 1 1
      Examples/UICatalog/Scenarios/ListColumns.cs
  14. 1 1
      Examples/UICatalog/Scenarios/ListViewWithSelection.cs
  15. 1 1
      Examples/UICatalog/Scenarios/Mouse.cs
  16. 8 8
      Examples/UICatalog/Scenarios/ScrollBarDemo.cs
  17. 7 7
      Examples/UICatalog/Scenarios/Shortcuts.cs
  18. 1 1
      Examples/UICatalog/Scenarios/SingleBackgroundWorker.cs
  19. 3 3
      Examples/UICatalog/Scenarios/ViewportSettings.cs
  20. 5 5
      Examples/UICatalog/Scenarios/Wizards.cs
  21. 4 29
      Examples/UICatalog/UICatalogTop.cs
  22. 0 1
      Terminal.Gui.Analyzers.Tests/Terminal.Gui.Analyzers.Tests.csproj
  23. 7 6
      Terminal.Gui/App/Application.Initialization.cs
  24. 3 4
      Terminal.Gui/App/Application.Run.cs
  25. 11 0
      Terminal.Gui/App/ApplicationImpl.cs
  26. 15 8
      Terminal.Gui/App/MainLoopSyncContext.cs
  27. 9 3
      Terminal.Gui/Drivers/AnsiResponseParser/Keyboard/CsiCursorPattern.cs
  28. 1 1
      Terminal.Gui/Drivers/NetDriver/NetEvents.cs
  29. 8 0
      Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs
  30. 0 6
      Terminal.Gui/Drivers/V2/IOutputBuffer.cs
  31. 39 0
      Terminal.Gui/Drivers/V2/InputProcessor.cs
  32. 0 7
      Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs
  33. 14 2
      Terminal.Gui/Drivers/V2/NetInput.cs
  34. 7 2
      Terminal.Gui/Drivers/V2/NetInputProcessor.cs
  35. 29 197
      Terminal.Gui/Drivers/V2/NetOutput.cs
  36. 163 0
      Terminal.Gui/Drivers/V2/OutputBase.cs
  37. 16 8
      Terminal.Gui/Drivers/V2/WindowsInput.cs
  38. 25 5
      Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs
  39. 302 171
      Terminal.Gui/Drivers/V2/WindowsOutput.cs
  40. 1 1
      Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs
  41. 5 3
      Terminal.Gui/ViewBase/Layout/Dim.cs
  42. 2 2
      Terminal.Gui/ViewBase/Layout/DimAuto.cs
  43. 13 6
      Terminal.Gui/ViewBase/Layout/DimFunc.cs
  44. 5 3
      Terminal.Gui/ViewBase/Layout/Pos.cs
  45. 1 1
      Terminal.Gui/ViewBase/Layout/PosAlign.cs
  46. 19 4
      Terminal.Gui/ViewBase/Layout/PosFunc.cs
  47. 32 11
      Terminal.Gui/ViewBase/View.Drawing.cs
  48. 9 4
      Terminal.Gui/ViewBase/View.Layout.cs
  49. 2 2
      Terminal.Gui/ViewBase/View.ScrollBars.cs
  50. 6 5
      Terminal.Gui/ViewBase/View.cs
  51. 20 0
      Terminal.Gui/ViewBase/ViewCollectionHelpers.cs
  52. 2 2
      Terminal.Gui/Views/FileDialogs/FileDialog.cs
  53. 7 5
      Terminal.Gui/Views/Menu/MenuBarv2.cs
  54. 2 2
      Terminal.Gui/Views/Menu/Menuv2.cs
  55. 1 1
      Terminal.Gui/Views/Menuv1/Menu.cs
  56. 4 4
      Terminal.Gui/Views/MessageBox.cs
  57. 1 1
      Terminal.Gui/Views/NumericUpDown.cs
  58. 2 2
      Terminal.Gui/Views/ScrollBar/ScrollBar.cs
  59. 4 4
      Terminal.Gui/Views/Shortcut.cs
  60. 4 4
      Terminal.Gui/Views/Wizard/Wizard.cs
  61. 78 58
      Tests/TerminalGuiFluentTesting/GuiTestContext.cs
  62. 61 31
      Tests/UnitTests/Application/SynchronizatonContextTests.cs
  63. 1 2
      Tests/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs
  64. 0 1
      Tests/UnitTests/View/Draw/ClearViewportTests.cs
  65. 1 1
      Tests/UnitTests/Views/LabelTests.cs
  66. 2 0
      Tests/UnitTests/Views/SpinnerViewTests.cs
  67. 9 1
      Tests/UnitTests/Views/TileViewTests.cs
  68. 33 17
      Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs
  69. 1 1
      Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.DimTypes.cs
  70. 2 1
      Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs
  71. 2 2
      Tests/UnitTestsParallelizable/View/Layout/Dim.FillTests.cs
  72. 48 5
      Tests/UnitTestsParallelizable/View/Layout/Dim.FuncTests.cs
  73. 8 8
      Tests/UnitTestsParallelizable/View/Layout/FrameTests.cs
  74. 49 5
      Tests/UnitTestsParallelizable/View/Layout/Pos.FuncTests.cs
  75. 2 2
      Tests/UnitTestsParallelizable/View/Layout/Pos.Tests.cs
  76. 4 4
      Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs
  77. 3 2
      Tests/UnitTestsParallelizable/View/Layout/SetRelativeLayoutTests.cs

+ 1 - 2
Directory.Packages.props

@@ -6,7 +6,6 @@
     <!-- Enable Nuget Source Link for github -->
     <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.11.0" />
-    <PackageVersion Include="Microsoft.CodeAnalysis.Features" Version="4.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="4.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.11.0" />
     <PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.11.0" />
@@ -25,7 +24,7 @@
     <PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />
     <PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
     <PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
-    <PackageVersion Include="SixLabors.ImageSharp" Version="[3.1.7,4)" />
+    <PackageVersion Include="SixLabors.ImageSharp" Version="[3.1.11,4)" />
     <PackageVersion Include="CsvHelper" Version="[33.0.1,34)" />
     <PackageVersion Include="Microsoft.DotNet.PlatformAbstractions" Version="[3.1.6,4)" />
     <PackageVersion Include="System.CommandLine" Version="[2.0.0-beta4.22272.1,3)" />

+ 1 - 1
Examples/UICatalog/Scenarios/Adornments.cs

@@ -35,7 +35,7 @@ public class Adornments : Scenario
             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);

+ 4 - 4
Examples/UICatalog/Scenarios/AllViewsTester.cs

@@ -101,7 +101,7 @@ public class AllViewsTester : Scenario
         {
             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 class AllViewsTester : Scenario
         {
             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 class AllViewsTester : Scenario
         {
             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 class AllViewsTester : Scenario
 
         _layoutEditor.Width = Dim.Fill (
                                         Dim.Func (
-                                                  () =>
+                                                  _ =>
                                                   {
                                                       if (_eventLog.NeedsLayout)
                                                       {

+ 1 - 1
Examples/UICatalog/Scenarios/Arrangement.cs

@@ -66,7 +66,7 @@ public class Arrangement : Scenario
         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";

+ 2 - 1
Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs

@@ -39,7 +39,6 @@ public class CharacterMap : Scenario
         {
             X = 0,
             Y = 1,
-            Width = Dim.Fill (Dim.Func (() => _categoryList!.Frame.Width)),
             Height = Dim.Fill (),
            // SchemeName = "Base"
 
@@ -172,6 +171,8 @@ public class CharacterMap : Scenario
         };
         top.Add (menu);
 
+        _charMap.Width = Dim.Fill (Dim.Func (v => v!.Frame.Width, _categoryList));
+
         _charMap.SelectedCodePoint = 0;
         _charMap.SetFocus ();
 

+ 1 - 1
Examples/UICatalog/Scenarios/ConfigurationEditor.cs

@@ -55,7 +55,7 @@ public class ConfigurationEditor : Scenario
         _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);

+ 1 - 1
Examples/UICatalog/Scenarios/DynamicMenuBar.cs

@@ -598,7 +598,7 @@ public class DynamicMenuBar : Scenario
                 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
             };
 

+ 1 - 1
Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentEditor.cs

@@ -100,7 +100,7 @@ public class AdornmentEditor : EditorBase
 
         _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
         };
 

+ 1 - 1
Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentsEditor.cs

@@ -122,7 +122,7 @@ public class AdornmentsEditor : EditorBase
         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;

+ 3 - 3
Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs

@@ -68,7 +68,7 @@ public class DimEditor : EditorBase
                 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 @@ public class DimEditor : EditorBase
         {
             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 @@ public class DimEditor : EditorBase
                 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
             };

+ 1 - 1
Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs

@@ -20,7 +20,7 @@ public class EventLog : ListView
         Y = 0;
 
         Width = Dim.Func (
-                          () =>
+                          _ =>
                           {
                               if (!IsInitialized)
                               {

+ 3 - 3
Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs

@@ -70,7 +70,7 @@ public class PosEditor : EditorBase
                 break;
             case PosFunc func:
                 _valueEdit.Enabled = true;
-                _value = func.Fn ();
+                _value = func.Fn (null);
                 _valueEdit!.Text = _value.ToString ();
 
                 break;
@@ -98,7 +98,7 @@ public class PosEditor : EditorBase
         {
             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 @@ public class PosEditor : EditorBase
                            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
                        };

+ 1 - 1
Examples/UICatalog/Scenarios/ListColumns.cs

@@ -252,7 +252,7 @@ public class ListColumns : Scenario
 
         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);

+ 1 - 1
Examples/UICatalog/Scenarios/ListViewWithSelection.cs

@@ -76,7 +76,7 @@ public class ListViewWithSelection : Scenario
             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,

+ 1 - 1
Examples/UICatalog/Scenarios/Mouse.cs

@@ -109,7 +109,7 @@ public class Mouse : Scenario
                                    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"
                                });

+ 8 - 8
Examples/UICatalog/Scenarios/ScrollBarDemo.cs

@@ -72,7 +72,7 @@ public class ScrollBarDemo : Scenario
             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 @@ public class ScrollBarDemo : Scenario
             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 @@ public class ScrollBarDemo : Scenario
             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 @@ public class ScrollBarDemo : Scenario
             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 @@ public class ScrollBarDemo : Scenario
             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 @@ public class ScrollBarDemo : Scenario
             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 @@ public class ScrollBarDemo : Scenario
             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 @@ public class ScrollBarDemo : Scenario
             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);

+ 7 - 7
Examples/UICatalog/Scenarios/Shortcuts.cs

@@ -43,14 +43,14 @@ public class Shortcuts : Scenario
         };
 
         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 @@ public class Shortcuts : Scenario
             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",

+ 1 - 1
Examples/UICatalog/Scenarios/SingleBackgroundWorker.cs

@@ -83,7 +83,7 @@ public class SingleBackgroundWorker : Scenario
             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";

+ 3 - 3
Examples/UICatalog/Scenarios/ViewportSettings.cs

@@ -124,8 +124,8 @@ public class ViewportSettings : Scenario
         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 class ViewportSettings : Scenario
         {
             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)),
         };
 

+ 5 - 5
Examples/UICatalog/Scenarios/Wizards.cs

@@ -282,11 +282,11 @@ public class Wizards : Scenario
 
                                            someText.Height = Dim.Fill (
                                                                        Dim.Func (
-                                                                                 () => someText.SuperView is { IsInitialized: true }
-                                                                                           ? someText.SuperView.SubViews
-                                                                                                     .First (view => view.Y.Has<PosAnchorEnd> (out _))
-                                                                                                     .Frame.Height
-                                                                                           : 1));
+                                                                                 v => someText.SuperView is { IsInitialized: true }
+                                                                                          ? someText.SuperView.SubViews
+                                                                                                    .First (view => view.Y.Has<PosAnchorEnd> (out _))
+                                                                                                    .Frame.Height
+                                                                                          : 1));
                                            var help = "This is helpful.";
                                            fourthStep.Add (someText);
 

+ 4 - 29
Examples/UICatalog/UICatalogTop.cs

@@ -404,20 +404,7 @@ public class UICatalogTop : Toplevel
             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 @@ public class UICatalogTop : Toplevel
             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",
@@ -595,8 +570,8 @@ public class UICatalogTop : Toplevel
         // 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 ()

+ 0 - 1
Terminal.Gui.Analyzers.Tests/Terminal.Gui.Analyzers.Tests.csproj

@@ -15,7 +15,6 @@
 		<PackageReference Include="coverlet.collector" />
 		<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
 		<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
-		<PackageReference Include="Microsoft.CodeAnalysis.Features" />
 		<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" />
 		<PackageReference Include="Microsoft.NET.Test.Sdk" />
 		<PackageReference Include="xunit" />

+ 7 - 6
Terminal.Gui/App/Application.Initialization.cs

@@ -212,14 +212,15 @@ public static partial class Application // Initialization (Init/Shutdown)
         // use reflection to get the list of drivers
         List<Type?> driverTypes = new ();
 
-        foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ())
+        // Only inspect the IConsoleDriver assembly
+        var asm = typeof (IConsoleDriver).Assembly;
+
+        foreach (Type? type in asm.GetTypes ())
         {
-            foreach (Type? type in asm.GetTypes ())
+            if (typeof (IConsoleDriver).IsAssignableFrom (type) &&
+                type is { IsAbstract: false, IsClass: true })
             {
-                if (typeof (IConsoleDriver).IsAssignableFrom (type) && !type.IsAbstract && type.IsClass)
-                {
-                    driverTypes.Add (type);
-                }
+                driverTypes.Add (type);
             }
         }
 

+ 3 - 4
Terminal.Gui/App/Application.Run.cs

@@ -205,6 +205,8 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
         toplevel.OnLoaded ();
 
+        LayoutAndDraw (true);
+
         if (PositionCursor ())
         {
             Driver?.UpdateCursor ();
@@ -212,9 +214,6 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
         NotifyNewRunState?.Invoke (toplevel, new (rs));
 
-        // Force an Idle event so that an Iteration (and Refresh) happen.
-        Invoke (() => { });
-
         return rs;
     }
 
@@ -535,7 +534,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
             return firstIteration;
         }
 
-        LayoutAndDraw ();
+        LayoutAndDraw (TopLevels.Any (v => v.NeedsLayout || v.NeedsDraw));
 
         if (PositionCursor ())
         {

+ 11 - 0
Terminal.Gui/App/ApplicationImpl.cs

@@ -275,6 +275,8 @@ public class ApplicationImpl : IApplication
         if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId)
         {
             action ();
+            WakeupMainLoop ();
+
             return;
         }
 
@@ -293,6 +295,15 @@ public class ApplicationImpl : IApplication
                                return false;
                            }
                           );
+
+        WakeupMainLoop ();
+
+        void WakeupMainLoop ()
+        {
+            // Ensure the action is executed in the main loop
+            // Wakeup mainloop if it's waiting for events
+            Application.MainLoop?.Wakeup ();
+        }
     }
 
     /// <inheritdoc />

+ 15 - 8
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 ();

+ 9 - 3
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 <see cref="IAnsiResponseParser"/>).
 ///     Handles navigation CSI key parsing such as <c>\x1b[A</c> (Cursor up)
-///     and <c>\x1b[1;5A</c> (Cursor up with Ctrl)
+///     and <c>\x1b[1;5A</c> (Cursor/Function with modifier(s))
 /// </summary>
 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<char, Key> _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 }
     };
 
     /// <inheritdoc/>

+ 1 - 1
Terminal.Gui/Drivers/NetDriver/NetEvents.cs

@@ -50,7 +50,7 @@ internal class NetEvents : IDisposable
 
     public InputResult? DequeueInput ()
     {
-        while (!_netEventsDisposed.Token.IsCancellationRequested)
+        while (_netEventsDisposed is { Token.IsCancellationRequested: false })
         {
             _winChange.Set ();
 

+ 8 - 0
Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs

@@ -96,6 +96,11 @@ internal class NetWinVTConsole
 
     public void Cleanup ()
     {
+        if (!FlushConsoleInputBuffer (_inputHandle))
+        {
+            throw new ApplicationException ($"Failed to flush input buffer, error code: {GetLastError ()}.");
+        }
+
         if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode))
         {
             throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}.");
@@ -123,4 +128,7 @@ internal class NetWinVTConsole
 
     [DllImport ("kernel32.dll")]
     private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool FlushConsoleInputBuffer (nint hConsoleInput);
 }

+ 0 - 6
Terminal.Gui/Drivers/V2/IOutputBuffer.cs

@@ -9,12 +9,6 @@ namespace Terminal.Gui.Drivers;
 /// </summary>
 public interface IOutputBuffer
 {
-    /// <summary>
-    ///     As performance is a concern, we keep track of the dirty lines and only refresh those.
-    ///     This is in addition to the dirty flag on each cell.
-    /// </summary>
-    public bool [] DirtyLines { get; }
-
     /// <summary>
     ///     The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
     /// </summary>

+ 39 - 0
Terminal.Gui/Drivers/V2/InputProcessor.cs

@@ -162,4 +162,43 @@ public abstract class InputProcessor<T> : IInputProcessor
     /// </summary>
     /// <param name="input"></param>
     protected abstract void ProcessAfterParsing (T input);
+
+    internal char _highSurrogate = '\0';
+
+    internal bool IsValidInput (Key key, out Key result)
+    {
+        result = key;
+
+        if (char.IsHighSurrogate ((char)key))
+        {
+            _highSurrogate = (char)key;
+
+            return false;
+        }
+
+        if (_highSurrogate > 0 && char.IsLowSurrogate ((char)key))
+        {
+            result = (KeyCode)new Rune (_highSurrogate, (char)key).Value;
+            _highSurrogate = '\0';
+
+            return true;
+        }
+
+        if (char.IsSurrogate ((char)key))
+        {
+            return false;
+        }
+
+        if (_highSurrogate > 0)
+        {
+            _highSurrogate = '\0';
+        }
+
+        if (key.KeyCode == 0)
+        {
+            return false;
+        }
+
+        return true;
+    }
 }

+ 0 - 7
Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs

@@ -25,7 +25,6 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
     private ConsoleDriverFacade<T> _facade;
     private Task _inputTask;
     private readonly ITimedEvents _timedEvents;
-    private readonly bool _isWindowsTerminal;
 
     private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
 
@@ -61,7 +60,6 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
         _inputProcessor = inputProcessor;
         _outputFactory = outputFactory;
         _loop = loop;
-        _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null;
     }
 
     /// <summary>
@@ -162,11 +160,6 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
                            _loop.AnsiRequestScheduler,
                            _loop.WindowSizeMonitor);
 
-            if (!_isWindowsTerminal)
-            {
-                Application.Force16Colors = _facade.Force16Colors = true;
-            }
-
             Application.Driver = _facade;
 
             _startupSemaphore.Release ();

+ 14 - 2
Terminal.Gui/Drivers/V2/NetInput.cs

@@ -40,6 +40,12 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
             }
         }
 
+        //Enable alternative screen buffer.
+        Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+
+        //Set cursor key to application.
+        Console.Out.Write (EscSeqUtils.CSI_HideCursor);
+
         Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
         Console.TreatControlCAsInput = true;
     }
@@ -68,8 +74,14 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
     public override void Dispose ()
     {
         base.Dispose ();
-        _adjustConsole?.Cleanup ();
-
         Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+
+        //Disable alternative screen buffer.
+        Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+
+        //Set cursor key to cursor.
+        Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
+
+        _adjustConsole?.Cleanup ();
     }
 }

+ 7 - 2
Terminal.Gui/Drivers/V2/NetInputProcessor.cs

@@ -41,8 +41,13 @@ public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
     protected override void ProcessAfterParsing (ConsoleKeyInfo input)
     {
         var key = KeyConverter.ToKey (input);
-        OnKeyDown (key);
-        OnKeyUp (key);
+
+        // If the key is not valid, we don't want to raise any events.
+        if (IsValidInput (key, out key))
+        {
+            OnKeyDown (key);
+            OnKeyUp (key);
+        }
     }
 
     /* For building test cases */

+ 29 - 197
Terminal.Gui/Drivers/V2/NetOutput.cs

@@ -6,15 +6,10 @@ namespace Terminal.Gui.Drivers;
 ///     Implementation of <see cref="IConsoleOutput"/> that uses native dotnet
 ///     methods e.g. <see cref="System.Console"/>
 /// </summary>
-public class NetOutput : IConsoleOutput
+public class NetOutput : OutputBase, IConsoleOutput
 {
     private readonly bool _isWinPlatform;
 
-    private CursorVisibility? _cachedCursorVisibility;
-
-    // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
-    private TextStyle _redrawTextStyle = TextStyle.None;
-
     /// <summary>
     ///     Creates a new instance of the <see cref="NetOutput"/> class.
     /// </summary>
@@ -30,176 +25,10 @@ public class NetOutput : IConsoleOutput
         {
             _isWinPlatform = true;
         }
-
-        //Enable alternative screen buffer.
-        Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
-
-        //Set cursor key to application.
-        Console.Out.Write (EscSeqUtils.CSI_HideCursor);
     }
 
     /// <inheritdoc/>
-    public void Write (ReadOnlySpan<char> text)
-    {
-        Console.Out.Write (text);
-    }
-
-    /// <inheritdoc/>
-    public void Write (IOutputBuffer buffer)
-    {
-        if (ConsoleDriver.RunningUnitTests)
-        {
-            return;
-        }
-
-        if (Console.WindowHeight < 1
-            || buffer.Contents.Length != buffer.Rows * buffer.Cols
-            || buffer.Rows != Console.WindowHeight)
-        {
-            //     return;
-        }
-
-        var top = 0;
-        var left = 0;
-        int rows = buffer.Rows;
-        int cols = buffer.Cols;
-        var output = new StringBuilder ();
-        Attribute? redrawAttr = null;
-        int lastCol = -1;
-
-        CursorVisibility? savedVisibility = _cachedCursorVisibility;
-        SetCursorVisibility (CursorVisibility.Invisible);
-
-        const int maxCharsPerRune = 2;
-        Span<char> runeBuffer = stackalloc char[maxCharsPerRune];
-
-        for (int row = top; row < rows; row++)
-        {
-            if (Console.WindowHeight < 1)
-            {
-                return;
-            }
-
-            if (!buffer.DirtyLines [row])
-            {
-                continue;
-            }
-
-            if (!SetCursorPositionImpl (0, row))
-            {
-                return;
-            }
-
-            buffer.DirtyLines [row] = false;
-            output.Clear ();
-
-            for (int col = left; col < cols; col++)
-            {
-                lastCol = -1;
-                var outputWidth = 0;
-
-                for (; col < cols; col++)
-                {
-                    if (!buffer.Contents [row, col].IsDirty)
-                    {
-                        if (output.Length > 0)
-                        {
-                            WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                        }
-                        else if (lastCol == -1)
-                        {
-                            lastCol = col;
-                        }
-
-                        if (lastCol + 1 < cols)
-                        {
-                            lastCol++;
-                        }
-
-                        continue;
-                    }
-
-                    if (lastCol == -1)
-                    {
-                        lastCol = col;
-                    }
-
-                    Attribute attr = buffer.Contents [row, col].Attribute.Value;
-
-                    // Performance: Only send the escape sequence if the attribute has changed.
-                    if (attr != redrawAttr)
-                    {
-                        redrawAttr = attr;
-
-                        EscSeqUtils.CSI_AppendForegroundColorRGB (
-                            output,
-                            attr.Foreground.R,
-                            attr.Foreground.G,
-                            attr.Foreground.B
-                        );
-
-                        EscSeqUtils.CSI_AppendBackgroundColorRGB (
-                            output,
-                            attr.Background.R,
-                            attr.Background.G,
-                            attr.Background.B
-                        );
-
-                        EscSeqUtils.CSI_AppendTextStyleChange (output, _redrawTextStyle, attr.Style);
-
-                        _redrawTextStyle = attr.Style;
-                    }
-
-                    outputWidth++;
-
-                    // Avoid Rune.ToString() by appending the rune chars.
-                    Rune rune = buffer.Contents [row, col].Rune;
-                    int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer);
-                    ReadOnlySpan<char> runeChars = runeBuffer[..runeCharsWritten];
-                    output.Append (runeChars);
-
-                    if (buffer.Contents [row, col].CombiningMarks.Count > 0)
-                    {
-                        // AtlasEngine does not support NON-NORMALIZED combining marks in a way
-                        // compatible with the driver architecture. Any CMs (except in the first col)
-                        // are correctly combined with the base char, but are ALSO treated as 1 column
-                        // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
-                        // 
-                        // For now, we just ignore the list of CMs.
-                        //foreach (var combMark in Contents [row, col].CombiningMarks) {
-                        //	output.Append (combMark);
-                        //}
-                        // WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                    }
-                    else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
-                    {
-                        WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                        SetCursorPositionImpl (col - 1, row);
-                    }
-
-                    buffer.Contents [row, col].IsDirty = false;
-                }
-            }
-
-            if (output.Length > 0)
-            {
-                SetCursorPositionImpl (lastCol, row);
-                Console.Out.Write (output);
-            }
-        }
-
-        foreach (SixelToRender s in Application.Sixel)
-        {
-            if (!string.IsNullOrWhiteSpace (s.SixelData))
-            {
-                SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
-                Console.Out.Write (s.SixelData);
-            }
-        }
-
-        SetCursorVisibility (savedVisibility ?? CursorVisibility.Default);
-        _cachedCursorVisibility = savedVisibility;
-    }
+    public void Write (ReadOnlySpan<char> text) { Console.Out.Write (text); }
 
     /// <inheritdoc/>
     public Size GetWindowSize ()
@@ -213,23 +42,37 @@ public class NetOutput : IConsoleOutput
         return new (Console.WindowWidth, Console.WindowHeight);
     }
 
-    private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+    /// <inheritdoc/>
+    public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
+
+    private Point? _lastCursorPosition;
+
+    /// <inheritdoc/>
+    protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
     {
-        SetCursorPositionImpl (lastCol, row);
-        Console.Out.Write (output);
-        output.Clear ();
-        lastCol += outputWidth;
-        outputWidth = 0;
+        EscSeqUtils.CSI_AppendForegroundColorRGB (
+                                                  output,
+                                                  attr.Foreground.R,
+                                                  attr.Foreground.G,
+                                                  attr.Foreground.B
+                                                 );
+
+        EscSeqUtils.CSI_AppendBackgroundColorRGB (
+                                                  output,
+                                                  attr.Background.R,
+                                                  attr.Background.G,
+                                                  attr.Background.B
+                                                 );
+
+        EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
     }
 
     /// <inheritdoc/>
-    public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
+    protected override void Write (StringBuilder output) { Console.Out.Write (output); }
 
-    private Point _lastCursorPosition;
-
-    private bool SetCursorPositionImpl (int col, int row)
+    protected override bool SetCursorPositionImpl (int col, int row)
     {
-        if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row)
+        if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == col && _lastCursorPosition.Value.Y == row)
         {
             return true;
         }
@@ -259,21 +102,10 @@ public class NetOutput : IConsoleOutput
     }
 
     /// <inheritdoc/>
-    public void Dispose ()
-    {
-        Console.ResetColor ();
-
-        //Disable alternative screen buffer.
-        Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
-
-        //Set cursor key to cursor.
-        Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
-
-        Console.Out.Close ();
-    }
+    public void Dispose () { }
 
     /// <inheritdoc/>
-    public void SetCursorVisibility (CursorVisibility visibility)
+    public override void SetCursorVisibility (CursorVisibility visibility)
     {
         Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
     }

+ 163 - 0
Terminal.Gui/Drivers/V2/OutputBase.cs

@@ -0,0 +1,163 @@
+namespace Terminal.Gui.Drivers;
+
+public abstract class OutputBase
+{
+    private CursorVisibility? _cachedCursorVisibility;
+
+    // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
+    private TextStyle _redrawTextStyle = TextStyle.None;
+
+    /// <inheritdoc/>
+    public virtual void Write (IOutputBuffer buffer)
+    {
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return;
+        }
+
+        if (Console.WindowHeight < 1
+            || buffer.Contents.Length != buffer.Rows * buffer.Cols
+            || buffer.Rows != Console.WindowHeight)
+        {
+            //     return;
+        }
+
+        var top = 0;
+        var left = 0;
+        int rows = buffer.Rows;
+        int cols = buffer.Cols;
+        var output = new StringBuilder ();
+        Attribute? redrawAttr = null;
+        int lastCol = -1;
+
+        CursorVisibility? savedVisibility = _cachedCursorVisibility;
+        SetCursorVisibility (CursorVisibility.Invisible);
+
+        const int maxCharsPerRune = 2;
+        Span<char> runeBuffer = stackalloc char [maxCharsPerRune];
+
+        for (int row = top; row < rows; row++)
+        {
+            if (Console.WindowHeight < 1)
+            {
+                return;
+            }
+
+            if (!SetCursorPositionImpl (0, row))
+            {
+                return;
+            }
+
+            output.Clear ();
+
+            for (int col = left; col < cols; col++)
+            {
+                lastCol = -1;
+                var outputWidth = 0;
+
+                for (; col < cols; col++)
+                {
+                    if (!buffer.Contents [row, col].IsDirty)
+                    {
+                        if (output.Length > 0)
+                        {
+                            WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                        }
+                        else if (lastCol == -1)
+                        {
+                            lastCol = col;
+                        }
+
+                        if (lastCol + 1 < cols)
+                        {
+                            lastCol++;
+                        }
+
+                        continue;
+                    }
+
+                    if (lastCol == -1)
+                    {
+                        lastCol = col;
+                    }
+
+                    Attribute attr = buffer.Contents [row, col].Attribute.Value;
+
+                    // Performance: Only send the escape sequence if the attribute has changed.
+                    if (attr != redrawAttr)
+                    {
+                        redrawAttr = attr;
+
+                        AppendOrWriteAttribute (output, attr, _redrawTextStyle);
+
+                        _redrawTextStyle = attr.Style;
+                    }
+
+                    outputWidth++;
+
+                    // Avoid Rune.ToString() by appending the rune chars.
+                    Rune rune = buffer.Contents [row, col].Rune;
+                    int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer);
+                    ReadOnlySpan<char> runeChars = runeBuffer [..runeCharsWritten];
+                    output.Append (runeChars);
+
+                    if (buffer.Contents [row, col].CombiningMarks.Count > 0)
+                    {
+                        // AtlasEngine does not support NON-NORMALIZED combining marks in a way
+                        // compatible with the driver architecture. Any CMs (except in the first col)
+                        // are correctly combined with the base char, but are ALSO treated as 1 column
+                        // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
+                        // 
+                        // For now, we just ignore the list of CMs.
+                        //foreach (var combMark in Contents [row, col].CombiningMarks) {
+                        //	output.Append (combMark);
+                        //}
+                        // WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                    }
+                    else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
+                    {
+                        WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                        SetCursorPositionImpl (col - 1, row);
+                    }
+
+                    buffer.Contents [row, col].IsDirty = false;
+                }
+            }
+
+            if (output.Length > 0)
+            {
+                SetCursorPositionImpl (lastCol, row);
+                Write (output);
+            }
+        }
+
+        foreach (SixelToRender s in Application.Sixel)
+        {
+            if (!string.IsNullOrWhiteSpace (s.SixelData))
+            {
+                SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
+                Console.Out.Write (s.SixelData);
+            }
+        }
+
+        SetCursorVisibility (savedVisibility ?? CursorVisibility.Default);
+        _cachedCursorVisibility = savedVisibility;
+    }
+
+    protected abstract void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle);
+
+    private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+    {
+        SetCursorPositionImpl (lastCol, row);
+        Write (output);
+        output.Clear ();
+        lastCol += outputWidth;
+        outputWidth = 0;
+    }
+
+    protected abstract void Write (StringBuilder output);
+
+    protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY);
+
+    public abstract void SetCursorVisibility (CursorVisibility visibility);
+}

+ 16 - 8
Terminal.Gui/Drivers/V2/WindowsInput.cs

@@ -4,7 +4,7 @@ using static Terminal.Gui.Drivers.WindowsConsole;
 
 namespace Terminal.Gui.Drivers;
 
-internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindowsInput
+internal class WindowsInput : ConsoleInput<InputRecord>, IWindowsInput
 {
     private readonly nint _inputHandle;
 
@@ -35,6 +35,9 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
 
     private readonly uint _originalConsoleMode;
 
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool FlushConsoleInputBuffer (nint hConsoleInput);
+
     public WindowsInput ()
     {
         Logging.Logger.LogInformation ($"Creating {nameof (WindowsInput)}");
@@ -50,16 +53,16 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
         _originalConsoleMode = v;
 
         uint newConsoleMode = _originalConsoleMode;
-        newConsoleMode |= (uint)(WindowsConsole.ConsoleModes.EnableMouseInput | WindowsConsole.ConsoleModes.EnableExtendedFlags);
-        newConsoleMode &= ~(uint)WindowsConsole.ConsoleModes.EnableQuickEditMode;
-        newConsoleMode &= ~(uint)WindowsConsole.ConsoleModes.EnableProcessedInput;
+        newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
+        newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
+        newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
         SetConsoleMode (_inputHandle, newConsoleMode);
     }
 
     protected override bool Peek ()
     {
         const int bufferSize = 1; // We only need to check if there's at least one event
-        nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<WindowsConsole.InputRecord> () * bufferSize);
+        nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
 
         try
         {
@@ -89,10 +92,10 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
         }
     }
 
-    protected override IEnumerable<WindowsConsole.InputRecord> Read ()
+    protected override IEnumerable<InputRecord> Read ()
     {
         const int bufferSize = 1;
-        nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<WindowsConsole.InputRecord> () * bufferSize);
+        nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
 
         try
         {
@@ -104,7 +107,7 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
 
             return numberEventsRead == 0
                        ? []
-                       : new [] { Marshal.PtrToStructure<WindowsConsole.InputRecord> (pRecord) };
+                       : new [] { Marshal.PtrToStructure<InputRecord> (pRecord) };
         }
         catch (Exception)
         {
@@ -123,6 +126,11 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
             return;
         }
 
+        if (!FlushConsoleInputBuffer (_inputHandle))
+        {
+            throw new ApplicationException ($"Failed to flush input buffer, error code: {Marshal.GetLastWin32Error ()}.");
+        }
+
         SetConsoleMode (_inputHandle, _originalConsoleMode);
     }
 }

+ 25 - 5
Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs

@@ -71,7 +71,8 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
     {
         var key = KeyConverter.ToKey (input);
 
-        if (key != (Key)0)
+        // If the key is not valid, we don't want to raise any events.
+        if (IsValidInput (key, out key))
         {
             OnKeyDown (key!);
             OnKeyUp (key!);
@@ -82,10 +83,29 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
     {
         var mouseFlags = MouseFlags.None;
 
-        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button1Pressed, MouseFlags.Button1Pressed, MouseFlags.Button1Released, 0);
-        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button2Pressed, MouseFlags.Button2Pressed, MouseFlags.Button2Released, 1);
-        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button4Pressed, MouseFlags.Button4Pressed, MouseFlags.Button4Released, 3);
-
+        mouseFlags = UpdateMouseFlags (
+                                       mouseFlags,
+                                       e.ButtonState,
+                                       WindowsConsole.ButtonState.Button1Pressed,
+                                       MouseFlags.Button1Pressed,
+                                       MouseFlags.Button1Released,
+                                       0);
+
+        mouseFlags = UpdateMouseFlags (
+                                       mouseFlags,
+                                       e.ButtonState,
+                                       WindowsConsole.ButtonState.Button2Pressed,
+                                       MouseFlags.Button2Pressed,
+                                       MouseFlags.Button2Released,
+                                       1);
+
+        mouseFlags = UpdateMouseFlags (
+                                       mouseFlags,
+                                       e.ButtonState,
+                                       WindowsConsole.ButtonState.Button4Pressed,
+                                       MouseFlags.Button4Pressed,
+                                       MouseFlags.Button4Released,
+                                       3);
 
         // Deal with button 3 separately because it is considered same as 'rightmost button'
         if (e.ButtonState.HasFlag (WindowsConsole.ButtonState.Button3Pressed) || e.ButtonState.HasFlag (WindowsConsole.ButtonState.RightmostButtonPressed))

+ 302 - 171
Terminal.Gui/Drivers/V2/WindowsOutput.cs

@@ -1,13 +1,12 @@
 #nullable enable
-using System.Buffers;
 using System.ComponentModel;
 using System.Runtime.InteropServices;
+using System.Text;
 using Microsoft.Extensions.Logging;
-using static Terminal.Gui.Drivers.WindowsConsole;
 
 namespace Terminal.Gui.Drivers;
 
-internal partial class WindowsOutput : IConsoleOutput
+internal partial class WindowsOutput : OutputBase, IConsoleOutput
 {
     [LibraryImport ("kernel32.dll", EntryPoint = "WriteConsoleW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
     [return: MarshalAs (UnmanagedType.Bool)]
@@ -19,11 +18,15 @@ internal partial class WindowsOutput : IConsoleOutput
         nint lpReserved
     );
 
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool CloseHandle (nint handle);
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    private static partial nint GetStdHandle (int nStdHandle);
 
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern nint CreateConsoleScreenBuffer (
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static partial bool CloseHandle (nint handle);
+
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    private static partial nint CreateConsoleScreenBuffer (
         DesiredAccess dwDesiredAccess,
         ShareMode dwShareMode,
         nint secutiryAttributes,
@@ -32,6 +35,7 @@ internal partial class WindowsOutput : IConsoleOutput
     );
 
     [DllImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
     private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX csbi);
 
     [Flags]
@@ -50,19 +54,52 @@ internal partial class WindowsOutput : IConsoleOutput
 
     internal static nint INVALID_HANDLE_VALUE = new (-1);
 
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool SetConsoleActiveScreenBuffer (nint handle);
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static partial bool SetConsoleActiveScreenBuffer (nint handle);
 
-    [DllImport ("kernel32.dll")]
-    private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, WindowsConsole.Coord dwCursorPosition);
+    [LibraryImport ("kernel32.dll")]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static partial bool SetConsoleCursorPosition (nint hConsoleOutput, WindowsConsole.Coord dwCursorPosition);
 
     [DllImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
     private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref WindowsConsole.ConsoleCursorInfo lpConsoleCursorInfo);
 
-    private readonly nint _screenBuffer;
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    public static partial bool SetConsoleTextAttribute (nint hConsoleOutput, ushort wAttributes);
+
+    [LibraryImport ("kernel32.dll")]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static partial bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
+
+    [LibraryImport ("kernel32.dll")]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static partial bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
+
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    private static partial WindowsConsole.Coord GetLargestConsoleWindowSize (
+        nint hConsoleOutput
+    );
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX consoleScreenBufferInfo);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static extern bool SetConsoleWindowInfo (
+        nint hConsoleOutput,
+        bool bAbsolute,
+        [In] ref WindowsConsole.SmallRect lpConsoleWindow
+    );
 
-    // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
-    private TextStyle _redrawTextStyle = TextStyle.None;
+    private const int STD_OUTPUT_HANDLE = -11;
+    private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
+    private readonly nint _outputHandle;
+    private nint _screenBuffer;
+    private readonly bool _isVirtualTerminal;
 
     public WindowsOutput ()
     {
@@ -73,13 +110,48 @@ internal partial class WindowsOutput : IConsoleOutput
             return;
         }
 
+        // Get the standard output handle which is the current screen buffer.
+        _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
+        GetConsoleMode (_outputHandle, out uint mode);
+        _isVirtualTerminal = (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
+
+        if (_isVirtualTerminal)
+        {
+            //Enable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+        }
+        else
+        {
+            CreateScreenBuffer ();
+
+            if (!GetConsoleMode (_screenBuffer, out mode))
+            {
+                throw new ApplicationException ($"Failed to get screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}.");
+            }
+
+            const uint ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002;
+
+            mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; // Disable wrap
+
+            if (!SetConsoleMode (_screenBuffer, mode))
+            {
+                throw new ApplicationException ($"Failed to set screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}.");
+            }
+
+            // Force 16 colors if not in virtual terminal mode.
+            Application.Force16Colors = true;
+        }
+    }
+
+    private void CreateScreenBuffer ()
+    {
         _screenBuffer = CreateConsoleScreenBuffer (
-                                                   DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
-                                                   ShareMode.FileShareRead | ShareMode.FileShareWrite,
-                                                   nint.Zero,
-                                                   1,
-                                                   nint.Zero
-                                                  );
+                                   DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
+                                   ShareMode.FileShareRead | ShareMode.FileShareWrite,
+                                   nint.Zero,
+                                   1,
+                                   nint.Zero
+                                  );
 
         if (_screenBuffer == INVALID_HANDLE_VALUE)
         {
@@ -99,212 +171,229 @@ internal partial class WindowsOutput : IConsoleOutput
 
     public void Write (ReadOnlySpan<char> str)
     {
-        if (!WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
+        if (!WriteConsole (_isVirtualTerminal ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
         {
             throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer.");
         }
     }
 
-    public void Write (IOutputBuffer buffer)
+    public Size ResizeBuffer (Size size)
     {
-        WindowsConsole.ExtendedCharInfo [] outputBuffer = new WindowsConsole.ExtendedCharInfo [buffer.Rows * buffer.Cols];
+        Size newSize = SetConsoleWindow (
+                                 (short)Math.Max (size.Width, 0),
+                                 (short)Math.Max (size.Height, 0));
 
-        // TODO: probably do need this right?
-        /*
-        if (!windowSize.IsEmpty && (windowSize.Width != buffer.Cols || windowSize.Height != buffer.Rows))
+        return newSize;
+    }
+
+    internal Size SetConsoleWindow (short cols, short rows)
+    {
+        var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
+        csbi.cbSize = (uint)Marshal.SizeOf (csbi);
+
+        if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
         {
-            return;
-        }*/
+            throw new Win32Exception (Marshal.GetLastWin32Error ());
+        }
 
-        var bufferCoords = new WindowsConsole.Coord
+        WindowsConsole.Coord maxWinSize = GetLargestConsoleWindowSize (_isVirtualTerminal ? _outputHandle : _screenBuffer);
+        short newCols = Math.Min (cols, maxWinSize.X);
+        short newRows = Math.Min (rows, maxWinSize.Y);
+        csbi.dwSize = new (newCols, Math.Max (newRows, (short)1));
+        csbi.srWindow = new (0, 0, newCols, newRows);
+        csbi.dwMaximumWindowSize = new (newCols, newRows);
+
+        if (!SetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
         {
-            X = (short)buffer.Cols, //Clip.Width,
-            Y = (short)buffer.Rows //Clip.Height
-        };
+            throw new Win32Exception (Marshal.GetLastWin32Error ());
+        }
+
+        var winRect = new WindowsConsole.SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0));
 
-        for (var row = 0; row < buffer.Rows; row++)
+        if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
         {
-            if (!buffer.DirtyLines [row])
-            {
-                continue;
-            }
+            //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
+            return new (cols, rows);
+        }
 
-            buffer.DirtyLines [row] = false;
+        SetConsoleOutputWindow (csbi);
 
-            for (var col = 0; col < buffer.Cols; col++)
-            {
-                int position = row * buffer.Cols + col;
-                outputBuffer [position].Attribute = buffer.Contents [row, col].Attribute.GetValueOrDefault ();
+        return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1);
+    }
 
-                if (buffer.Contents [row, col].IsDirty == false)
-                {
-                    outputBuffer [position].Empty = true;
-                    outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
+    private void SetConsoleOutputWindow (WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX csbi)
+    {
+        if ((_isVirtualTerminal
+                 ? _outputHandle
+                 : _screenBuffer) != nint.Zero && !SetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
+        {
+            throw new Win32Exception (Marshal.GetLastWin32Error ());
+        }
+    }
 
-                    continue;
-                }
+    public override void Write (IOutputBuffer outputBuffer)
+    {
+        _force16Colors = Application.Driver!.Force16Colors;
+        _everythingStringBuilder = new StringBuilder ();
+
+        // for 16 color mode we will write to a backing buffer then flip it to the active one at the end to avoid jitter.
+        _consoleBuffer = 0;
+        if (_force16Colors)
+        {
+            if (_isVirtualTerminal)
+            {
+                _consoleBuffer = _outputHandle;
+            }
+            else
+            {
+                _consoleBuffer = _screenBuffer;
+            }
+        }
+        else
+        {
+            _consoleBuffer = _outputHandle;
+        }
 
-                outputBuffer [position].Empty = false;
+        base.Write (outputBuffer);
 
-                if (buffer.Contents [row, col].Rune.IsBmp)
-                {
-                    outputBuffer [position].Char = (char)buffer.Contents [row, col].Rune.Value;
-                }
-                else
+        try
+        {
+            if (_force16Colors && !_isVirtualTerminal)
+            {
+                SetConsoleActiveScreenBuffer (_consoleBuffer);
+            }
+            else
+            {
+                var span = _everythingStringBuilder.ToString ().AsSpan (); // still allocates the string
+
+                var result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero);
+                if (!result)
                 {
-                    //outputBuffer [position].Empty = true;
-                    outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
+                    int err = Marshal.GetLastWin32Error ();
 
-                    if (buffer.Contents [row, col].Rune.GetColumns () > 1 && col + 1 < buffer.Cols)
+                    if (err != 0)
                     {
-                        // TODO: This is a hack to deal with non-BMP and wide characters.
-                        col++;
-                        position = row * buffer.Cols + col;
-                        outputBuffer [position].Empty = false;
-                        outputBuffer [position].Char = ' ';
+                        throw new Win32Exception (err);
                     }
                 }
             }
         }
-
-        var damageRegion = new WindowsConsole.SmallRect
+        catch (Exception e)
         {
-            Top = 0,
-            Left = 0,
-            Bottom = (short)buffer.Rows,
-            Right = (short)buffer.Cols
-        };
+            Logging.Logger.LogError ($"Error: {e.Message} in {nameof (WindowsOutput)}");
 
-        //size, ExtendedCharInfo [] charInfoBuffer, Coord , SmallRect window,
-        if (!ConsoleDriver.RunningUnitTests
-            && !WriteToConsole (
-                                new (buffer.Cols, buffer.Rows),
-                                outputBuffer,
-                                bufferCoords,
-                                damageRegion,
-                                Application.Driver!.Force16Colors))
-        {
-            int err = Marshal.GetLastWin32Error ();
-
-            if (err != 0)
+            if (!ConsoleDriver.RunningUnitTests)
             {
-                throw new Win32Exception (err);
+                throw;
             }
         }
-
-        WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
     }
-
-    public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [] charInfoBuffer, WindowsConsole.Coord bufferSize, WindowsConsole.SmallRect window, bool force16Colors)
+    /// <inheritdoc />
+    protected override void Write (StringBuilder output)
     {
+        if (output.Length == 0)
+        {
+            return;
+        }
 
-        //Debug.WriteLine ("WriteToConsole");
+        var str = output.ToString ();
 
-        //if (_screenBuffer == nint.Zero)
-        //{
-        //    ReadFromConsoleOutput (size, bufferSize, ref window);
-        //}
+        if (_force16Colors && !_isVirtualTerminal)
+        {
+            var a = str.ToCharArray ();
+            WriteConsole (_screenBuffer,a ,(uint)a.Length, out _, nint.Zero);
+        }
+        else
+        {
+            _everythingStringBuilder.Append (str);
+        }
+    }
 
-        var result = false;
+    /// <inheritdoc />
+    protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
+    {
+        var force16Colors = Application.Force16Colors;
 
         if (force16Colors)
         {
-            var i = 0;
-            WindowsConsole.CharInfo [] ci = new WindowsConsole.CharInfo [charInfoBuffer.Length];
-
-            foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer)
+            if (_isVirtualTerminal)
             {
-                ci [i++] = new ()
-                {
-                    Char = new () { UnicodeChar = info.Char },
-                    Attributes =
-                        (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
-                };
+                output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
+                output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
+                EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
+            }
+            else
+            {
+                var as16ColorInt = (ushort)((int)attr.Foreground.GetClosestNamedColor16 () | ((int)attr.Background.GetClosestNamedColor16 () << 4));
+                SetConsoleTextAttribute (_screenBuffer, as16ColorInt);
             }
-
-            result = WriteConsoleOutput (_screenBuffer, ci, bufferSize, new () { X = window.Left, Y = window.Top }, ref window);
         }
         else
         {
-            StringBuilder stringBuilder = new();
-
-            stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
-            EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, 0, 0);
+            EscSeqUtils.CSI_AppendForegroundColorRGB (output, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B);
+            EscSeqUtils.CSI_AppendBackgroundColorRGB (output, attr.Background.R, attr.Background.G, attr.Background.B);
+            EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
+        }
+    }
 
-            Attribute? prev = null;
 
-            foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer)
-            {
-                Attribute attr = info.Attribute;
+    private Size? _lastSize;
+    private Size? _lastWindowSizeBeforeMaximized;
+    private bool _lockResize;
 
-                if (attr != prev)
-                {
-                    prev = attr;
-                    EscSeqUtils.CSI_AppendForegroundColorRGB (stringBuilder, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B);
-                    EscSeqUtils.CSI_AppendBackgroundColorRGB (stringBuilder, attr.Background.R, attr.Background.G, attr.Background.B);
-                    EscSeqUtils.CSI_AppendTextStyleChange (stringBuilder, _redrawTextStyle, attr.Style);
-                    _redrawTextStyle = attr.Style;
-                }
-
-                if (info.Char != '\x1b')
-                {
-                    if (!info.Empty)
-                    {
-                        stringBuilder.Append (info.Char);
-                    }
-                }
-                else
-                {
-                    stringBuilder.Append (' ');
-                }
-            }
-
-            stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
-            stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
+    public Size GetWindowSize ()
+    {
+        if (_lockResize)
+        {
+            return _lastSize!.Value;
+        }
 
-            // TODO: Potentially could stackalloc whenever reasonably small (<= 8 kB?) write buffer is needed.
-            char [] rentedWriteArray = ArrayPool<char>.Shared.Rent (minimumLength: stringBuilder.Length);
-            try
-            {
-                Span<char> writeBuffer = rentedWriteArray.AsSpan(0, stringBuilder.Length);
-                stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length);
+        var newSize = GetWindowSize (out _);
+        Size largestWindowSize = GetLargestConsoleWindowSize ();
 
-                // Supply console with the new content.
-                result = WriteConsole (_screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero);
-            }
-            finally
+        if (_lastWindowSizeBeforeMaximized is null && newSize == largestWindowSize)
+        {
+            _lastWindowSizeBeforeMaximized = _lastSize;
+        }
+        else if (_lastWindowSizeBeforeMaximized is { } && newSize != largestWindowSize)
+        {
+            if (newSize != _lastWindowSizeBeforeMaximized)
             {
-                ArrayPool<char>.Shared.Return (rentedWriteArray);
+                newSize = _lastWindowSizeBeforeMaximized.Value;
             }
 
-            foreach (SixelToRender sixel in Application.Sixel)
-            {
-                SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y);
-                WriteConsole (_screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
-            }
+            _lastWindowSizeBeforeMaximized = null;
         }
 
-        if (!result)
+        if (_lastSize == null || _lastSize != newSize)
         {
-            int err = Marshal.GetLastWin32Error ();
-
-            if (err != 0)
+            // User is resizing the screen, they can only ever resize the active
+            // buffer since. We now however have issue because background offscreen
+            // buffer will be wrong size, recreate it to ensure it doesn't result in
+            // differing active and back buffer sizes (which causes flickering of window size)
+            Size? bufSize = null;
+            while (bufSize != newSize)
             {
-                throw new Win32Exception (err);
+                _lockResize = true;
+                bufSize = ResizeBuffer (newSize);
             }
+
+            _lockResize = false;
+            _lastSize = newSize;
         }
 
-        return result;
+        return newSize;
     }
 
-    public Size GetWindowSize ()
+    public Size GetWindowSize (out WindowsConsole.Coord cursorPosition)
     {
         var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
         csbi.cbSize = (uint)Marshal.SizeOf (csbi);
 
-        if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
+        if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
         {
             //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
+            cursorPosition = default;
             return Size.Empty;
         }
 
@@ -312,18 +401,45 @@ internal partial class WindowsOutput : IConsoleOutput
                        csbi.srWindow.Right - csbi.srWindow.Left + 1,
                        csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
 
+        cursorPosition = csbi.dwCursorPosition;
         return sz;
     }
 
+    private Size GetLargestConsoleWindowSize ()
+    {
+        WindowsConsole.Coord maxWinSize = GetLargestConsoleWindowSize (_isVirtualTerminal ? _outputHandle : _screenBuffer);
+
+        return new (maxWinSize.X, maxWinSize.Y);
+    }
+
+    /// <inheritdoc />
+    protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY)
+    {
+        if (_force16Colors && !_isVirtualTerminal)
+        {
+            SetConsoleCursorPosition (_screenBuffer, new ((short)screenPositionX, (short)screenPositionY));
+        }
+        else
+        {
+            // CSI codes are 1 indexed
+            _everythingStringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
+            EscSeqUtils.CSI_AppendCursorPosition (_everythingStringBuilder, screenPositionY + 1, screenPositionX + 1);
+        }
+
+        _lastCursorPosition = new (screenPositionX, screenPositionY);
+
+        return true;
+    }
+
     /// <inheritdoc/>
-    public void SetCursorVisibility (CursorVisibility visibility)
+    public override void SetCursorVisibility (CursorVisibility visibility)
     {
         if (ConsoleDriver.RunningUnitTests)
         {
             return;
         }
 
-        if (Application.Driver!.Force16Colors)
+        if (!_isVirtualTerminal)
         {
             var info = new WindowsConsole.ConsoleCursorInfo
             {
@@ -342,22 +458,34 @@ internal partial class WindowsOutput : IConsoleOutput
         }
     }
 
-    private Point _lastCursorPosition;
+    private Point? _lastCursorPosition;
 
     /// <inheritdoc/>
     public void SetCursorPosition (int col, int row)
     {
-        if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row)
+        if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == col && _lastCursorPosition.Value.Y == row)
         {
             return;
         }
 
         _lastCursorPosition = new (col, row);
 
-        SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
+        if (_isVirtualTerminal)
+        {
+            var sb = new StringBuilder ();
+            EscSeqUtils.CSI_AppendCursorPosition (sb, row + 1, col + 1);
+            Write (sb.ToString ());
+        }
+        else
+        {
+            SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
+        }
     }
 
     private bool _isDisposed;
+    private bool _force16Colors;
+    private nint _consoleBuffer;
+    private StringBuilder _everythingStringBuilder;
 
     /// <inheritdoc/>
     public void Dispose ()
@@ -367,16 +495,19 @@ internal partial class WindowsOutput : IConsoleOutput
             return;
         }
 
-        if (_screenBuffer != nint.Zero)
+        if (_isVirtualTerminal)
+        {
+            //Disable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+        }
+        else
         {
-            try
+            if (_screenBuffer != nint.Zero)
             {
                 CloseHandle (_screenBuffer);
             }
-            catch (Exception e)
-            {
-                Logging.Logger.LogError (e, "Error trying to close screen buffer handle in WindowsOutput via interop method");
-            }
+
+            _screenBuffer = nint.Zero;
         }
 
         _isDisposed = true;

+ 1 - 1
Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs

@@ -62,7 +62,7 @@ internal partial class WindowsConsole
         InputRecord inputRecord = default;
         uint numberEventsRead = 0;
 
-        while (!_inputReadyCancellationTokenSource!.IsCancellationRequested)
+        while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
         {
             try
             {

+ 5 - 3
Terminal.Gui/ViewBase/Layout/Dim.cs

@@ -141,12 +141,14 @@ public abstract record Dim : IEqualityOperators<Dim, Dim, bool>
     public static Dim? Fill (Dim margin) { return new DimFill (margin); }
 
     /// <summary>
-    ///     Creates a function <see cref="Dim"/> object that computes the dimension by executing the provided function.
+    ///     Creates a function <see cref="Dim"/> 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.
     /// </summary>
     /// <param name="function">The function to be executed.</param>
-    /// <returns>The <see cref="Dim"/> returned from the function.</returns>
-    public static Dim Func (Func<int> function) { return new DimFunc (function); }
+    /// <param name="view">The view where the data will be retrieved.</param>
+    /// <returns>The <see cref="Dim"/> returned from the function based on the passed view.</returns>
+    public static Dim Func (Func<View?, int> function, View? view = null) { return new DimFunc (function, view); }
 
     /// <summary>Creates a <see cref="Dim"/> object that tracks the Height of the specified <see cref="View"/>.</summary>
     /// <returns>The height <see cref="Dim"/> of the other <see cref="View"/>.</returns>

+ 2 - 2
Terminal.Gui/ViewBase/Layout/DimAuto.cs

@@ -48,7 +48,7 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
                     us.TextFormatter.ConstrainToSize = us.TextFormatter.FormatAndGetSize (new (int.Min (autoMax, screenX4), screenX4));
                 }
 
-                textSize = us.TextFormatter.ConstrainToWidth!.Value;
+                textSize = us.TextFormatter.ConstrainToWidth ?? 0;
             }
             else
             {
@@ -81,7 +81,7 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
             {
                 // TOOD: All the below is a naive implementation. It may be possible to optimize this.
 
-                List<View> includedSubViews = us.InternalSubViews.ToList ();
+                List<View> 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`.

+ 13 - 6
Terminal.Gui/ViewBase/Layout/DimFunc.cs

@@ -2,22 +2,29 @@
 namespace Terminal.Gui.ViewBase;
 
 /// <summary>
-///     Represents a function <see cref="Dim"/> object that computes the dimension by executing the provided function.
+///     Represents a function <see cref="Dim"/> object that computes the dimension based on the passed view and by
+///     executing the provided function.
 /// </summary>
 /// <remarks>
 ///     This is a low-level API that is typically used internally by the layout system. Use the various static
 ///     methods on the <see cref="Dim"/> class to create <see cref="Dim"/> objects instead.
 /// </remarks>
 /// <param name="Fn">The function that computes the dimension. If this function throws <see cref="LayoutException"/>... </param>
-public record DimFunc (Func<int> Fn) : Dim
+/// <param name="View">The <see cref="Dim"/> returned from the function based on the passed view.</param>
+public record DimFunc (Func<View?, int> Fn, View? View = null) : Dim
 {
     /// <summary>
     ///     Gets the function that computes the dimension.
     /// </summary>
-    public Func<int> Fn { get; } = Fn;
+    public Func<View?, int> Fn { get; } = Fn;
+
+    /// <summary>
+    ///     Gets the passed view that the dimension is based on.
+    /// </summary>
+    public View? View { get; } = View;
 
     /// <inheritdoc/>
-    public override string ToString () { return $"DimFunc({Fn ()})"; }
+    public override string ToString () { return $"DimFunc({Fn (View)})"; }
 
-    internal override int GetAnchor (int size) { return Fn (); }
-}
+    internal override int GetAnchor (int size) { return Fn (View); }
+}

+ 5 - 3
Terminal.Gui/ViewBase/Layout/Pos.cs

@@ -220,12 +220,14 @@ public abstract record Pos
     public static Pos Center () { return new PosCenter (); }
 
     /// <summary>
-    ///     Creates a <see cref="Pos"/> object that computes the position by executing the provided function. The function
-    ///     will be called every time the position is needed.
+    ///     Creates a <see cref="Pos"/> 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.
     /// </summary>
     /// <param name="function">The function to be executed.</param>
+    /// <param name="view">The view where the data will be retrieved.</param>
     /// <returns>The <see cref="Pos"/> returned from the function.</returns>
-    public static Pos Func (Func<int> function) { return new PosFunc (function); }
+    public static Pos Func (Func<View?, int> function, View? view = null) { return new PosFunc (function, view); }
 
     /// <summary>Creates a percentage <see cref="Pos"/> object</summary>
     /// <returns>The percent <see cref="Pos"/> object.</returns>

+ 1 - 1
Terminal.Gui/ViewBase/Layout/PosAlign.cs

@@ -117,7 +117,7 @@ public record PosAlign : Pos
         }
         else
         {
-            groupViews = us.SuperView!.SubViews.Where (v => HasGroupId (v, dimension, GroupId)).ToList ();
+            groupViews = us.SuperView!.SubViews.Snapshot ().Where (v => HasGroupId (v, dimension, GroupId)).ToList ();
         }
 
         AlignAndUpdateGroup (GroupId, groupViews, dimension, superviewDimension);

+ 19 - 4
Terminal.Gui/ViewBase/Layout/PosFunc.cs

@@ -4,11 +4,26 @@ namespace Terminal.Gui.ViewBase;
 /// <summary>
 ///     Represents a position that is computed by executing a function that returns an integer position.
 /// </summary>
-/// <param name="Fn">The function that computes the dimension. If this function throws <see cref="LayoutException"/>... </param>
-public record PosFunc (Func<int> Fn) : Pos
+/// <remarks>
+///     This is a low-level API that is typically used internally by the layout system. Use the various static
+///     methods on the <see cref="Pos"/> class to create <see cref="Pos"/> objects instead.
+/// </remarks>
+/// <param name="Fn">The function that computes the position. If this function throws <see cref="LayoutException"/>... </param>
+/// <param name="View">The <see cref="Pos"/> returned from the function based on the passed view.</param>
+public record PosFunc (Func<View?, int> Fn, View? View = null) : Pos
 {
+    /// <summary>
+    ///     Gets the function that computes the position.
+    /// </summary>
+    public Func<View?, int> Fn { get; } = Fn;
+
+    /// <summary>
+    ///     Gets the passed view that the position is based on.
+    /// </summary>
+    public View? View { get; } = View;
+
     /// <inheritdoc/>
-    public override string ToString () { return $"PosFunc({Fn ()})"; }
+    public override string ToString () { return $"PosFunc({Fn (View)})"; }
 
-    internal override int GetAnchor (int size) { return Fn (); }
+    internal override int GetAnchor (int size) { return Fn (View); }
 }

+ 32 - 11
Terminal.Gui/ViewBase/View.Drawing.cs

@@ -1,5 +1,6 @@
 #nullable enable
 using System.ComponentModel;
+using System.Diagnostics;
 
 namespace Terminal.Gui.ViewBase;
 
@@ -12,7 +13,8 @@ public partial class View // Drawing APIs
     /// <param name="force">If <see langword="true"/>, <see cref="View.SetNeedsDraw()"/> will be called on each view to force it to be drawn.</param>
     internal static void Draw (IEnumerable<View> views, bool force)
     {
-        IEnumerable<View> 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 ();
@@ -111,6 +113,26 @@ public partial class View // Drawing APIs
             Border?.AdvanceDrawIndicator ();
 
             ClearNeedsDraw ();
+
+            if (this is not Adornment && SuperView is not Adornment)
+            {
+                // Parent
+                Debug.Assert (Margin!.Parent == this);
+                Debug.Assert (Border!.Parent == this);
+                Debug.Assert (Padding!.Parent == this);
+
+                // SubViewNeedsDraw is set to false by ClearNeedsDraw.
+                Debug.Assert (SubViewNeedsDraw == false);
+                Debug.Assert (Margin!.SubViewNeedsDraw == false);
+                Debug.Assert (Border!.SubViewNeedsDraw == false);
+                Debug.Assert (Padding!.SubViewNeedsDraw == false);
+
+                // NeedsDraw is set to false by ClearNeedsDraw.
+                Debug.Assert (NeedsDraw == false);
+                Debug.Assert (Margin!.NeedsDraw == false);
+                Debug.Assert (Border!.NeedsDraw == false);
+                Debug.Assert (Padding!.NeedsDraw == false);
+            }
         }
 
         // ------------------------------------
@@ -549,7 +571,7 @@ public partial class View // Drawing APIs
         }
 
         // Draw the subviews in reverse order to leverage clipping.
-        foreach (View view in InternalSubViews.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))
@@ -720,8 +742,7 @@ public partial class View // Drawing APIs
     /// </remarks>
     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)
@@ -806,8 +827,8 @@ public partial class View // Drawing APIs
             adornment.Parent?.SetSubViewNeedsDraw ();
         }
 
-        // There was multiple enumeration error here, so calling ToArray - probably a stop gap
-        foreach (View subview in SubViews.ToArray ())
+        // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap
+        foreach (View subview in InternalSubViews.Snapshot ())
         {
             if (subview.Frame.IntersectsWith (viewPortRelativeRegion))
             {
@@ -846,22 +867,23 @@ public partial class View // Drawing APIs
         NeedsDrawRect = Rectangle.Empty;
         SubViewNeedsDraw = false;
 
-        if (Margin is { } && Margin.Thickness != Thickness.Empty)
+        if (Margin is { } && (Margin.Thickness != Thickness.Empty || Margin.SubViewNeedsDraw || Margin.NeedsDraw))
         {
             Margin?.ClearNeedsDraw ();
         }
 
-        if (Border is { } && Border.Thickness != Thickness.Empty)
+        if (Border is { } && (Border.Thickness != Thickness.Empty || Border.SubViewNeedsDraw || Border.NeedsDraw))
         {
             Border?.ClearNeedsDraw ();
         }
 
-        if (Padding is { } && Padding.Thickness != Thickness.Empty)
+        if (Padding is { } && (Padding.Thickness != Thickness.Empty || Padding.SubViewNeedsDraw || Padding.NeedsDraw))
         {
             Padding?.ClearNeedsDraw ();
         }
 
-        foreach (View subview in SubViews)
+        // There was multiple enumeration error here, so calling new snapshot collection - probably a stop gap
+        foreach (View subview in InternalSubViews.Snapshot ())
         {
             subview.ClearNeedsDraw ();
         }
@@ -876,7 +898,6 @@ public partial class View // Drawing APIs
         {
             LineCanvas.Clear ();
         }
-
     }
 
     #endregion NeedsDraw

+ 9 - 4
Terminal.Gui/ViewBase/View.Layout.cs

@@ -419,7 +419,10 @@ public partial class View // Layout APIs
         {
             LayoutSubViews ();
 
-            // Debug.Assert(!NeedsLayout);
+            // A layout was performed so a draw is needed
+            // NeedsLayout may still be true if a dependent View still needs layout after SubViewsLaidOut event
+            SetNeedsDraw ();
+
             return true;
         }
 
@@ -635,7 +638,7 @@ public partial class View // Layout APIs
 
         List<View> redo = new ();
 
-        foreach (View v in ordered)
+        foreach (View v in ordered.Snapshot ())
         {
             if (!v.Layout (contentSize))
             {
@@ -700,7 +703,7 @@ public partial class View // Layout APIs
     ///     Override to perform tasks after the <see cref="View"/> has been resized or the layout has
     ///     otherwise changed.
     /// </remarks>
-    protected virtual void OnSubViewsLaidOut (LayoutEventArgs args) { }
+    protected virtual void OnSubViewsLaidOut (LayoutEventArgs args) { Debug.Assert (!NeedsLayout); }
 
     /// <summary>Raised after all sub-views have been laid out.</summary>
     /// <remarks>
@@ -761,10 +764,12 @@ public partial class View // Layout APIs
 
         // TODO: Optimize this - see Setting_Thickness_Causes_Adornment_SubView_Layout
         // Use a stack to avoid recursion
-        Stack<View> stack = new (SubViews);
+        Stack<View> stack = new (InternalSubViews.Snapshot ().ToList ());
 
         while (stack.Count > 0)
         {
+            Debug.Assert (stack.Peek () is { });
+
             View current = stack.Pop ();
 
             if (!current.NeedsLayout)

+ 2 - 2
Terminal.Gui/ViewBase/View.ScrollBars.cs

@@ -73,7 +73,7 @@ public partial class View
 
         scrollBar.Height = Dim.Fill (
                                      Dim.Func (
-                                               () =>
+                                               _ =>
                                                {
                                                    if (_horizontalScrollBar.IsValueCreated)
                                                    {
@@ -98,7 +98,7 @@ public partial class View
 
         scrollBar.Width = Dim.Fill (
                                     Dim.Func (
-                                              () =>
+                                              _ =>
                                               {
                                                   if (_verticalScrollBar.IsValueCreated)
                                                   {

+ 6 - 5
Terminal.Gui/ViewBase/View.cs

@@ -245,12 +245,13 @@ public partial class View : IDisposable, ISupportInitializeNotification
             }
         }
 
-        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)).
-        }
+        // 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);

+ 20 - 0
Terminal.Gui/ViewBase/ViewCollectionHelpers.cs

@@ -0,0 +1,20 @@
+namespace Terminal.Gui.ViewBase;
+
+internal static class ViewCollectionHelpers
+{
+    /// <summary>Returns a defensive copy of any <see cref="IEnumerable{T}"/>.</summary>
+    internal static View [] Snapshot (this IEnumerable<View> source)
+    {
+        if (source is IList<View> 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<View>(list).ToArray())
+            }
+        }
+
+        // Anything else (LINQ result, iterator block, etc.) we just enumerate.
+        return source.ToArray (); // Safe because it’s not shared mutable state
+    }
+}

+ 2 - 2
Terminal.Gui/Views/FileDialogs/FileDialog.cs

@@ -154,7 +154,7 @@ public class FileDialog : Dialog, IDesignable
             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 @@ public class FileDialog : Dialog, IDesignable
         return (Style.IconProvider.GetIconWithOptionalSpace (fsi) + fsi.Name).Trim ();
     }
 
-    private int CalculateOkButtonPosX ()
+    private int CalculateOkButtonPosX (View? _)
     {
         if (!IsInitialized || !_btnOk.IsInitialized || !_btnCancel.IsInitialized)
         {

+ 7 - 5
Terminal.Gui/Views/Menu/MenuBarv2.cs

@@ -397,10 +397,10 @@ public class MenuBarv2 : Menuv2, IDesignable
 
         // If the active Application Popover is part of this MenuBar, hide it.
         if (Application.Popover?.GetActivePopover () is PopoverMenu popoverMenu
-            && popoverMenu?.Root?.SuperMenuItem?.SuperView == this)
+            && popoverMenu.Root?.SuperMenuItem?.SuperView == this)
         {
             // Logging.Debug ($"{Title} - Calling Application.Popover?.Hide ({popoverMenu.Title})");
-            Application.Popover?.Hide (popoverMenu);
+            Application.Popover.Hide (popoverMenu);
         }
 
         if (menuBarItem is null)
@@ -416,6 +416,7 @@ public class MenuBarv2 : Menuv2, IDesignable
         if (menuBarItem.PopoverMenu?.Root is { })
         {
             menuBarItem.PopoverMenu.Root.SuperMenuItem = menuBarItem;
+            menuBarItem.PopoverMenu.Root.SchemeName = SchemeName;
         }
 
         // Logging.Debug ($"{Title} - \"{menuBarItem.PopoverMenu?.Title}\".MakeVisible");
@@ -423,14 +424,15 @@ public class MenuBarv2 : Menuv2, IDesignable
 
         menuBarItem.Accepting += OnMenuItemAccepted;
 
-        menuBarItem.PopoverMenu!.Root.SchemeName = SchemeName;
-
         return;
 
         void OnMenuItemAccepted (object? sender, EventArgs args)
         {
             // Logging.Debug ($"{Title} - OnMenuItemAccepted");
-            menuBarItem.PopoverMenu!.VisibleChanged -= OnMenuItemAccepted;
+            if (menuBarItem.PopoverMenu is { })
+            {
+                menuBarItem.PopoverMenu.VisibleChanged -= OnMenuItemAccepted;
+            }
 
             if (Active && menuBarItem.PopoverMenu is { Visible: false })
             {

+ 2 - 2
Terminal.Gui/Views/Menu/Menuv2.cs

@@ -93,8 +93,8 @@ public class Menuv2 : Bar
                 }
             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;
         }

+ 1 - 1
Terminal.Gui/Views/Menuv1/Menu.cs

@@ -527,7 +527,7 @@ internal sealed class Menu : View
 
     private void Application_UnGrabbedMouse (object? sender, ViewEventArgs a)
     {
-        if (_host.IsMenuOpen)
+        if (_host is { IsMenuOpen: true })
         {
             _host.CloseAllMenus ();
         }

+ 4 - 4
Terminal.Gui/Views/MessageBox.cs

@@ -394,12 +394,12 @@ public static class MessageBox
         };
 
         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)

+ 1 - 1
Terminal.Gui/Views/NumericUpDown.cs

@@ -62,7 +62,7 @@ public class NumericUpDown<T> : View where T : notnull
             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,

+ 2 - 2
Terminal.Gui/Views/ScrollBar/ScrollBar.cs

@@ -40,11 +40,11 @@ public class ScrollBar : View, IOrientation, IDesignable
         // 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 ()
         {

+ 4 - 4
Terminal.Gui/Views/Shortcut.cs

@@ -106,8 +106,8 @@ public class Shortcut : View, IOrientation, IDesignable
     {
         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 @@ public class Shortcut : View, IOrientation, IDesignable
 
         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 @@ public class Shortcut : View, IOrientation, IDesignable
         }
 
         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;

+ 4 - 4
Terminal.Gui/Views/Wizard/Wizard.cs

@@ -490,7 +490,7 @@ public class Wizard : Dialog
 
             step.Height = Dim.Fill (
                                     Dim.Func (
-                                              () => IsInitialized
+                                              v => IsInitialized
                                                         ? SubViews.First (view => view.Y.Has<PosAnchorEnd> (out _)).Frame.Height + 1
                                                         : 1)); // for button frame (+1 for lineView)
             step.Width = Dim.Fill ();
@@ -502,9 +502,9 @@ public class Wizard : Dialog
 
             step.Height = Dim.Fill (
                                     Dim.Func (
-                                              () => IsInitialized
-                                                        ? SubViews.First (view => view.Y.Has<PosAnchorEnd> (out _)).Frame.Height + 1
-                                                        : 2)); // for button frame (+1 for lineView)
+                                              v => IsInitialized
+                                                       ? SubViews.First (view => view.Y.Has<PosAnchorEnd> (out _)).Frame.Height + 1
+                                                       : 2)); // for button frame (+1 for lineView)
             step.Width = Dim.Fill ();
         }
     }

+ 78 - 58
Tests/TerminalGuiFluentTesting/GuiTestContext.cs

@@ -25,75 +25,95 @@ public class GuiTestContext : IDisposable
 
     internal GuiTestContext (Func<Toplevel> topLevelBuilder, int width, int height, V2TestDriver driver)
     {
-        IApplication origApp = ApplicationImpl.Instance;
-        ILogger? origLogger = Logging.Logger;
-        _logsSb = new ();
-        _driver = driver;
-
-        _netInput = new (_cts.Token);
-        _winInput = new (_cts.Token);
-
-        _output.Size = new (width, height);
-
-        var v2 = new ApplicationV2 (
-                                    () => _netInput,
-                                    () => _output,
-                                    () => _winInput,
-                                    () => _output);
-
-        var booting = new SemaphoreSlim (0, 1);
-
         lock (_threadLock)
         {
-            // Start the application in a background thread
-            _runTask = Task.Run (
-                                 () =>
-                                 {
-                                     try
-                                     {
-                                         ApplicationImpl.ChangeInstance (v2);
+            IApplication origApp = ApplicationImpl.Instance;
+            ILogger? origLogger = Logging.Logger;
+            _logsSb = new ();
+            _driver = driver;
 
-                                         ILogger logger = LoggerFactory.Create (
-                                                                                builder =>
-                                                                                    builder.SetMinimumLevel (LogLevel.Trace)
-                                                                                           .AddProvider (new TextWriterLoggerProvider (new StringWriter (_logsSb))))
-                                                                       .CreateLogger ("Test Logging");
-                                         Logging.Logger = logger;
+            _netInput = new (_cts.Token);
+            _winInput = new (_cts.Token);
 
-                                         v2.Init (null, GetDriverName ());
+            _output.Size = new (width, height);
 
-                                         booting.Release ();
+            var v2 = new ApplicationV2 (
+                                        () => _netInput,
+                                        () => _output,
+                                        () => _winInput,
+                                        () => _output);
 
-                                         Toplevel t = topLevelBuilder ();
-                                         t.Closed += (s, e) => { _finished = true; };
-                                         Application.Run (t); // This will block, but it's on a background thread now
+            var booting = new SemaphoreSlim (0, 1);
 
-                                         t.Dispose ();
-                                         Application.Shutdown ();
-                                     }
-                                     catch (OperationCanceledException)
-                                     { }
-                                     catch (Exception ex)
-                                     {
-                                         _ex = ex;
-                                     }
-                                     finally
+            // Start the application in a background thread
+            _runTask = Task.Run (() =>
+                                 {
+                                     while (Application.Top is { })
                                      {
-                                         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
+            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 ()

+ 61 - 31
Tests/UnitTests/Application/SynchronizatonContextTests.cs

@@ -21,43 +21,73 @@ public class SyncrhonizationContextTests
         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<WindowsConsole.InputRecord>), "v2win")]
+    [InlineData (typeof (ConsoleDriverFacade<ConsoleKeyInfo>), "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]

+ 1 - 2
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)

+ 0 - 1
Tests/UnitTests/View/Draw/ClearViewportTests.cs

@@ -114,7 +114,6 @@ public class ClearViewportTests (ITestOutputHelper output)
         superView.BeginInit ();
         superView.EndInit ();
         superView.LayoutSubViews ();
-
         superView.Draw ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (

+ 1 - 1
Tests/UnitTests/Views/LabelTests.cs

@@ -94,7 +94,7 @@ public class LabelTests (ITestOutputHelper output)
     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);

+ 2 - 0
Tests/UnitTests/Views/SpinnerViewTests.cs

@@ -11,6 +11,8 @@ public class SpinnerViewTests (ITestOutputHelper output)
     [InlineData (false)]
     public void TestSpinnerView_AutoSpin (bool callStop)
     {
+        ConsoleDriver.RunningUnitTests = true;
+
         SpinnerView view = GetSpinnerView ();
 
         Assert.Empty (Application.MainLoop.TimedEvents.Timeouts);

+ 9 - 1
Tests/UnitTests/Views/TileViewTests.cs

@@ -819,6 +819,10 @@ public class TileViewTests (ITestOutputHelper output)
                              Assert.Equal (1, myReusableView.DisposalCount);
                          }
                         );
+
+        Assert.NotNull (Application.Top);
+        Application.Top.Dispose ();
+        Application.Shutdown ();
     }
 
     [Theory]
@@ -848,6 +852,10 @@ public class TileViewTests (ITestOutputHelper output)
                              Assert.True (myReusableView.DisposalCount >= 1);
                          }
                         );
+
+        Assert.NotNull (Application.Top);
+        Application.Top.Dispose ();
+        Application.Shutdown ();
     }
 
     [Fact]
@@ -1606,7 +1614,7 @@ public class TileViewTests (ITestOutputHelper output)
         var ex = Assert.Throws<ArgumentException> (() => tileView.SetSplitterPos (0, Pos.Right (tileView)));
         Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosView", ex.Message);
 
-        ex = Assert.Throws<ArgumentException> (() => tileView.SetSplitterPos (0, Pos.Func (() => 1)));
+        ex = Assert.Throws<ArgumentException> (() => 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

+ 33 - 17
Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs

@@ -10,7 +10,7 @@ public class NeedsDrawTests
         View view = new () { Width = 0, Height = 0 };
         view.BeginInit ();
         view.EndInit ();
-        Assert.True (view.NeedsDraw);
+        Assert.False (view.NeedsDraw);
 
         //Assert.False (view.SubViewNeedsDraw);
     }
@@ -70,14 +70,16 @@ public class NeedsDrawTests
         view.NeedsDraw = false;
 
         view.BeginInit ();
-        Assert.True (view.NeedsDraw); // Because layout is still needed
+        Assert.False (view.NeedsDraw); // Because layout is still needed
 
         view.Layout ();
-        Assert.False (view.NeedsDraw);
+        // NeedsDraw is true after layout and NeedsLayout is false if SubViewsLaidOut doesn't call SetNeedsLayout
+        Assert.True (view.NeedsDraw);
+        Assert.False (view.NeedsLayout);
     }
 
     [Fact]
-    public void NeedsDraw_False_After_EndInit ()
+    public void NeedsDraw_True_After_EndInit_Where_Call_Layout ()
     {
         var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single };
         Assert.True (view.NeedsDraw);
@@ -96,7 +98,7 @@ public class NeedsDrawTests
     }
 
     [Fact]
-    public void NeedsDraw_After_SetLayoutNeeded ()
+    public void NeedsDraw_After_SetLayoutNeeded_And_Layout ()
     {
         var view = new View { Width = 2, Height = 2 };
         Assert.True (view.NeedsDraw);
@@ -107,8 +109,12 @@ public class NeedsDrawTests
         Assert.False (view.NeedsLayout);
 
         view.SetNeedsLayout ();
-        Assert.True (view.NeedsDraw);
+        Assert.False (view.NeedsDraw);
         Assert.True (view.NeedsLayout);
+
+        view.Layout ();
+        Assert.True (view.NeedsDraw);
+        Assert.False (view.NeedsLayout);
     }
 
     [Fact]
@@ -121,21 +127,27 @@ public class NeedsDrawTests
         Assert.False (view.NeedsDraw);
         Assert.False (view.NeedsLayout);
 
-        // SRL won't change anything since the view is Absolute
+        // SRL won't change anything since the view frame wasn't changed
         view.SetRelativeLayout (Application.Screen.Size);
         Assert.False (view.NeedsDraw);
 
         view.SetNeedsLayout ();
 
-        // SRL won't change anything since the view is Absolute
+        // SRL won't change anything since the view frame wasn't changed
+        // SRL doesn't depend on NeedsLayout, but LayoutSubViews does
         view.SetRelativeLayout (Application.Screen.Size);
+        Assert.False (view.NeedsDraw);
+        Assert.True (view.NeedsLayout);
+
+        view.Layout ();
         Assert.True (view.NeedsDraw);
+        Assert.False (view.NeedsLayout);
 
         view.NeedsDraw = false;
 
-        // SRL won't change anything since the view is Absolute. However, Layout has not been called
+        // SRL won't change anything since the view frame wasn't changed. However, Layout has not been called
         view.SetRelativeLayout (new (10, 10));
-        Assert.True (view.NeedsDraw);
+        Assert.False (view.NeedsDraw);
     }
 
     [Fact]
@@ -149,17 +161,20 @@ public class NeedsDrawTests
             Width = Dim.Fill (),
             Height = Dim.Fill ()
         };
-        Assert.True (superView.NeedsDraw);
+
+        // A layout wasn't called yet, so NeedsDraw is still empty
+        Assert.False (superView.NeedsDraw);
 
         superView.Add (view);
-        Assert.True (view.NeedsDraw);
-        Assert.True (superView.NeedsDraw);
+        // A layout wasn't called yet, so NeedsDraw is still empty
+        Assert.False (view.NeedsDraw);
+        Assert.False (superView.NeedsDraw);
 
         superView.BeginInit ();
-        Assert.True (view.NeedsDraw);
-        Assert.True (superView.NeedsDraw);
+        Assert.False (view.NeedsDraw);
+        Assert.False (superView.NeedsDraw);
 
-        superView.EndInit ();
+        superView.EndInit (); // Call Layout
         Assert.True (view.NeedsDraw);
         Assert.True (superView.NeedsDraw);
 
@@ -177,9 +192,10 @@ public class NeedsDrawTests
             Width = Dim.Fill (),
             Height = Dim.Fill ()
         };
-        Assert.True (superView.NeedsDraw);
+        Assert.False (superView.NeedsDraw);
 
         superView.Layout ();
+        Assert.True (superView.NeedsDraw);
 
         superView.NeedsDraw = false;
         superView.SetRelativeLayout (new (10, 10));

+ 1 - 1
Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.DimTypes.cs

@@ -179,7 +179,7 @@ public partial class DimAutoTests
     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));

+ 2 - 1
Tests/UnitTestsParallelizable/View/Layout/Dim.AutoTests.PosTypes.cs

@@ -577,6 +577,7 @@ public partial class DimAutoTests
         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
@@ -638,7 +639,7 @@ public partial class DimAutoTests
             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));

+ 2 - 2
Tests/UnitTestsParallelizable/View/Layout/Dim.FillTests.cs

@@ -123,12 +123,12 @@ public class DimFillTests (ITestOutputHelper output)
     [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));
     }

+ 48 - 5
Tests/UnitTestsParallelizable/View/Layout/Dim.FuncTests.cs

@@ -10,8 +10,8 @@ public class DimFuncTests (ITestOutputHelper output)
     [Fact]
     public void DimFunc_Equal ()
     {
-        Func<int> f1 = () => 0;
-        Func<int> f2 = () => 0;
+        Func<View, int> f1 = _ => 0;
+        Func<View, int> f2 = _ => 0;
 
         Dim dim1 = Func (f1);
         Dim dim2 = Func (f1);
@@ -20,7 +20,7 @@ public class DimFuncTests (ITestOutputHelper output)
         dim2 = Func (f2);
         Assert.NotEqual (dim1, dim2);
 
-        f2 = () => 1;
+        f2 = _ => 1;
         dim2 = Func (f2);
         Assert.NotEqual (dim1, dim2);
     }
@@ -29,7 +29,7 @@ public class DimFuncTests (ITestOutputHelper output)
     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 class DimFuncTests (ITestOutputHelper output)
     [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<View, int> f1 = v => v.Frame.Width;
+        Func<View, int> 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);
+    }
 }

+ 8 - 8
Tests/UnitTestsParallelizable/View/Layout/FrameTests.cs

@@ -61,10 +61,10 @@ public class FrameTests
         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 @@ public class FrameTests
     {
         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);
         }
     }
 

+ 49 - 5
Tests/UnitTestsParallelizable/View/Layout/Pos.FuncTests.cs

@@ -9,14 +9,14 @@ public class PosFuncTests (ITestOutputHelper output)
     [Fact]
     public void PosFunc_Equal ()
     {
-        Func<int> f1 = () => 0;
-        Func<int> f2 = () => 0;
+        Func<View, int> f1 = _ => 0;
+        Func<View, int> 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 class PosFuncTests (ITestOutputHelper output)
     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 class PosFuncTests (ITestOutputHelper output)
     [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<View, int> f1 = v => v.Frame.X;
+        Func<View, int> 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);
+    }
 }

+ 2 - 2
Tests/UnitTestsParallelizable/View/Layout/Pos.Tests.cs

@@ -21,7 +21,7 @@ public class PosTests
     [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 class PosTests
     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";

+ 4 - 4
Tests/UnitTestsParallelizable/View/Layout/SetLayoutTests.cs

@@ -597,7 +597,7 @@ public class SetLayoutTests : GlobalTestSetup
         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 class SetLayoutTests : GlobalTestSetup
         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 class SetLayoutTests : GlobalTestSetup
         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 class SetLayoutTests : GlobalTestSetup
         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);
 

+ 3 - 2
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 class SetRelativeLayoutTests
         };
         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