Browse Source

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

Tig 3 weeks ago
parent
commit
d11f7becf7
100 changed files with 1268 additions and 1358 deletions
  1. 1 1
      Directory.Packages.props
  2. 1 1
      Examples/CommunityToolkitExample/Program.cs
  3. 1 1
      Examples/ReactiveExample/Program.cs
  4. 2 2
      Examples/UICatalog/Scenario.cs
  5. 1 1
      Examples/UICatalog/Scenarios/AllViewsTester.cs
  6. 8 8
      Examples/UICatalog/Scenarios/Bars.cs
  7. 28 1
      Examples/UICatalog/Scenarios/ColorPicker.cs
  8. 14 3
      Examples/UICatalog/Scenarios/CombiningMarks.cs
  9. 1 1
      Examples/UICatalog/Scenarios/ConfigurationEditor.cs
  10. 1 1
      Examples/UICatalog/Scenarios/CsvEditor.cs
  11. 1 1
      Examples/UICatalog/Scenarios/LineDrawing.cs
  12. 3 3
      Examples/UICatalog/Scenarios/Mazing.cs
  13. 27 27
      Examples/UICatalog/Scenarios/Shortcuts.cs
  14. 2 2
      Examples/UICatalog/Scenarios/SingleBackgroundWorker.cs
  15. 8 8
      Examples/UICatalog/Scenarios/Sliders.cs
  16. 4 7
      Examples/UICatalog/Scenarios/SyntaxHighlighting.cs
  17. 1 1
      Examples/UICatalog/Scenarios/Themes.cs
  18. 6 6
      Examples/UICatalog/Scenarios/TreeUseCases.cs
  19. 0 157
      Examples/UICatalog/Scenarios/TrueColors.cs
  20. 1 1
      Examples/UICatalog/Scenarios/WindowsAndFrameViews.cs
  21. 2 2
      Examples/UICatalog/UICatalog.cs
  22. 1 1
      Examples/UICatalog/UICatalogTop.cs
  23. 5 5
      Terminal.Gui/App/Application.Current.cs
  24. 5 11
      Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
  25. 32 32
      Terminal.Gui/App/ApplicationImpl.Run.cs
  26. 6 6
      Terminal.Gui/App/ApplicationImpl.cs
  27. 1 1
      Terminal.Gui/App/ApplicationNavigation.cs
  28. 4 4
      Terminal.Gui/App/ApplicationPopover.cs
  29. 6 5
      Terminal.Gui/App/IApplication.cs
  30. 1 1
      Terminal.Gui/App/IPopover.cs
  31. 2 2
      Terminal.Gui/App/Keyboard/KeyboardImpl.cs
  32. 4 4
      Terminal.Gui/App/MainLoop/ApplicationMainLoop.cs
  33. 3 3
      Terminal.Gui/App/Mouse/MouseImpl.cs
  34. 1 1
      Terminal.Gui/App/PopoverBaseImpl.cs
  35. 3 3
      Terminal.Gui/App/Toplevel/ToplevelTransitionManager.cs
  36. 86 70
      Terminal.Gui/Drawing/Cell.cs
  37. 49 0
      Terminal.Gui/Drawing/GraphemeHelper.cs
  38. 4 5
      Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs
  39. 13 17
      Terminal.Gui/Drivers/DriverImpl.cs
  40. 3 3
      Terminal.Gui/Drivers/IDriver.cs
  41. 4 4
      Terminal.Gui/Drivers/IOutputBuffer.cs
  42. 5 28
      Terminal.Gui/Drivers/OutputBase.cs
  43. 72 142
      Terminal.Gui/Drivers/OutputBufferImpl.cs
  44. 76 12
      Terminal.Gui/Text/StringExtensions.cs
  45. 169 159
      Terminal.Gui/Text/TextFormatter.cs
  46. 1 1
      Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs
  47. 4 4
      Terminal.Gui/ViewBase/Adornment/ShadowView.cs
  48. 19 1
      Terminal.Gui/ViewBase/View.Drawing.Primitives.cs
  49. 7 7
      Terminal.Gui/ViewBase/View.Drawing.cs
  50. 2 2
      Terminal.Gui/ViewBase/View.Hierarchy.cs
  51. 13 13
      Terminal.Gui/ViewBase/View.Layout.cs
  52. 6 6
      Terminal.Gui/ViewBase/View.Navigation.cs
  53. 13 13
      Terminal.Gui/Views/Autocomplete/AutocompleteFilepathContext.cs
  54. 2 2
      Terminal.Gui/Views/Autocomplete/ISuggestionGenerator.cs
  55. 1 1
      Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs
  56. 10 6
      Terminal.Gui/Views/Autocomplete/SingleWordSuggestionGenerator.cs
  57. 25 54
      Terminal.Gui/Views/CharMap/CharMap.cs
  58. 1 1
      Terminal.Gui/Views/Color/ColorBar.cs
  59. 3 2
      Terminal.Gui/Views/Color/HueBar.cs
  60. 1 1
      Terminal.Gui/Views/Dialog.cs
  61. 6 6
      Terminal.Gui/Views/Menuv1/Menu.cs
  62. 4 4
      Terminal.Gui/Views/Menuv1/MenuBar.cs
  63. 25 25
      Terminal.Gui/Views/Slider/Slider.cs
  64. 4 4
      Terminal.Gui/Views/TableView/TreeTableSource.cs
  65. 17 17
      Terminal.Gui/Views/TextInput/TextField.cs
  66. 58 52
      Terminal.Gui/Views/TextInput/TextModel.cs
  67. 27 28
      Terminal.Gui/Views/TextInput/TextView.cs
  68. 3 3
      Terminal.Gui/Views/Toplevel.cs
  69. 21 21
      Terminal.Gui/Views/TreeView/Branch.cs
  70. 1 1
      Terminal.Gui/Views/Wizard/Wizard.cs
  71. 4 4
      Tests/IntegrationTests/FluentTests/GuiTestContextKeyEventTests.cs
  72. 4 4
      Tests/IntegrationTests/FluentTests/GuiTestContextTests.cs
  73. 23 23
      Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs
  74. 2 2
      Tests/IntegrationTests/FluentTests/NavigationTests.cs
  75. 16 16
      Tests/IntegrationTests/FluentTests/PopverMenuTests.cs
  76. 3 1
      Tests/IntegrationTests/UICatalog/ScenarioTests.cs
  77. 2 2
      Tests/StressTests/ApplicationStressTests.cs
  78. 2 2
      Tests/StressTests/ScenariosStressTests.cs
  79. 1 1
      Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationLifecycle.cs
  80. 2 2
      Tests/TerminalGuiFluentTesting/GuiTestContext.Navigation.cs
  81. 4 4
      Tests/TerminalGuiFluentTesting/GuiTestContext.ViewBase.cs
  82. 12 12
      Tests/UnitTests/Application/Application.NavigationTests.cs
  83. 14 14
      Tests/UnitTests/Application/ApplicationImplBeginEndTests.cs
  84. 31 31
      Tests/UnitTests/Application/ApplicationImplTests.cs
  85. 7 7
      Tests/UnitTests/Application/ApplicationPopoverTests.cs
  86. 5 5
      Tests/UnitTests/Application/ApplicationScreenTests.cs
  87. 55 55
      Tests/UnitTests/Application/ApplicationTests.cs
  88. 39 39
      Tests/UnitTests/Application/Mouse/ApplicationMouseEnterLeaveTests.cs
  89. 8 8
      Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs
  90. 1 1
      Tests/UnitTests/Application/SessionTokenTests.cs
  91. 2 2
      Tests/UnitTests/Application/SynchronizatonContextTests.cs
  92. 15 15
      Tests/UnitTests/Dialogs/DialogTests.cs
  93. 7 7
      Tests/UnitTests/Dialogs/MessageBoxTests.cs
  94. 13 18
      Tests/UnitTests/DriverAssert.cs
  95. 8 8
      Tests/UnitTests/Drivers/ClipRegionTests.cs
  96. 13 13
      Tests/UnitTests/View/Adornment/AdornmentSubViewTests.cs
  97. 16 16
      Tests/UnitTests/View/Adornment/MarginTests.cs
  98. 5 5
      Tests/UnitTests/View/Draw/ClipTests.cs
  99. 9 9
      Tests/UnitTests/View/Draw/DrawTests.cs
  100. 3 3
      Tests/UnitTests/View/Keyboard/KeyBindingsTests.cs

+ 1 - 1
Directory.Packages.props

@@ -18,7 +18,7 @@
     <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[9.0.0,10)" />
     <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6" />
     <PackageVersion Include="System.IO.Abstractions" Version="[22.0.16,23)" />
-    <PackageVersion Include="Wcwidth" Version="[3.0.0,)" />
+    <PackageVersion Include="Wcwidth" Version="[4.0.0,)" />
     <PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21.2,2)" />
     <PackageVersion Include="Serilog" Version="4.2.0" />
     <PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />

+ 1 - 1
Examples/CommunityToolkitExample/Program.cs

@@ -16,7 +16,7 @@ public static class Program
         Services = ConfigureServices ();
         Application.Init ();
         Application.Run (Services.GetRequiredService<LoginView> ());
-        Application.Current?.Dispose ();
+        Application.TopRunnable?.Dispose ();
         Application.Shutdown ();
     }
 

+ 1 - 1
Examples/ReactiveExample/Program.cs

@@ -16,7 +16,7 @@ public static class Program
         RxApp.MainThreadScheduler = TerminalScheduler.Default;
         RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;
         Application.Run (new LoginView (new LoginViewModel ()));
-        Application.Current.Dispose ();
+        Application.TopRunnable.Dispose ();
         Application.Shutdown ();
     }
 }

+ 2 - 2
Examples/UICatalog/Scenario.cs

@@ -221,7 +221,7 @@ public class Scenario : IDisposable
 
     private void OnApplicationSessionBegun (object? sender, SessionTokenEventArgs e)
     {
-        SubscribeAllSubViews (Application.Current!);
+        SubscribeAllSubViews (Application.TopRunnable!);
 
         _demoKeys = GetDemoKeyStrokes ();
 
@@ -241,7 +241,7 @@ public class Scenario : IDisposable
 
         return;
 
-        // Get a list of all subviews under Application.Current (and their subviews, etc.)
+        // Get a list of all subviews under Application.TopRunnable (and their subviews, etc.)
         // and subscribe to their DrawComplete event
         void SubscribeAllSubViews (View view)
         {

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

@@ -28,7 +28,7 @@ public class AllViewsTester : Scenario
 
     public override void Main ()
     {
-        // Don't create a sub-win (Scenario.Win); just use Application.Current
+        // Don't create a sub-win (Scenario.Win); just use Application.TopRunnable
         Application.Init ();
 
         var app = new Window

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

@@ -28,7 +28,7 @@ public class Bars : Scenario
     // QuitKey and it only sticks if changed after init
     private void App_Loaded (object sender, EventArgs e)
     {
-        Application.Current!.Title = GetQuitKeyAndName ();
+        Application.TopRunnable!.Title = GetQuitKeyAndName ();
 
         ObservableCollection<string> eventSource = new ();
         ListView eventLog = new ListView ()
@@ -41,7 +41,7 @@ public class Bars : Scenario
             Source = new ListWrapper<string> (eventSource)
         };
         eventLog.Border!.Thickness = new (0, 1, 0, 0);
-        Application.Current.Add (eventLog);
+        Application.TopRunnable.Add (eventLog);
 
         FrameView menuBarLikeExamples = new ()
         {
@@ -51,7 +51,7 @@ public class Bars : Scenario
             Width = Dim.Fill () - Dim.Width (eventLog),
             Height = Dim.Percent(33),
         };
-        Application.Current.Add (menuBarLikeExamples);
+        Application.TopRunnable.Add (menuBarLikeExamples);
 
         Label label = new Label ()
         {
@@ -98,7 +98,7 @@ public class Bars : Scenario
             Width = Dim.Fill () - Dim.Width (eventLog),
             Height = Dim.Percent (33),
         };
-        Application.Current.Add (menuLikeExamples);
+        Application.TopRunnable.Add (menuLikeExamples);
 
         label = new Label ()
         {
@@ -212,7 +212,7 @@ public class Bars : Scenario
             Width = Dim.Width (menuLikeExamples),
             Height = Dim.Percent (33),
         };
-        Application.Current.Add (statusBarLikeExamples);
+        Application.TopRunnable.Add (statusBarLikeExamples);
 
         label = new Label ()
         {
@@ -249,7 +249,7 @@ public class Bars : Scenario
         ConfigStatusBar (bar);
         statusBarLikeExamples.Add (bar);
 
-        foreach (FrameView frameView in Application.Current.SubViews.Where (f => f is FrameView)!)
+        foreach (FrameView frameView in Application.TopRunnable.SubViews.Where (f => f is FrameView)!)
         {
             foreach (Bar barView in frameView.SubViews.Where (b => b is Bar)!)
             {
@@ -269,8 +269,8 @@ public class Bars : Scenario
 
     //private void SetupContentMenu ()
     //{
-    //    Application.Current.Add (new Label { Text = "Right Click for Context Menu", X = Pos.Center (), Y = 4 });
-    //    Application.Current.MouseClick += ShowContextMenu;
+    //    Application.TopRunnable.Add (new Label { Text = "Right Click for Context Menu", X = Pos.Center (), Y = 4 });
+    //    Application.TopRunnable.MouseClick += ShowContextMenu;
     //}
 
     //private void ShowContextMenu (object s, MouseEventEventArgs e)

+ 28 - 1
Examples/UICatalog/Scenarios/ColorPicker.cs

@@ -3,7 +3,7 @@ using System.Collections.Generic;
 
 namespace UICatalog.Scenarios;
 
-[ScenarioMetadata ("ColorPicker", "Color Picker.")]
+[ScenarioMetadata ("ColorPicker", "Color Picker and TrueColor demonstration.")]
 [ScenarioCategory ("Colors")]
 [ScenarioCategory ("Controls")]
 public class ColorPickers : Scenario
@@ -220,6 +220,33 @@ public class ColorPickers : Scenario
                                            };
         app.Add (cbShowName);
 
+        var lblDriverName = new Label
+        {
+            Y = Pos.Bottom (cbShowName) + 1, Text = $"Driver is `{Application.Driver?.GetName ()}`:"
+        };
+        bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false;
+
+        var cbSupportsTrueColor = new CheckBox
+        {
+            X = Pos.Right (lblDriverName) + 1,
+            Y = Pos.Top (lblDriverName),
+            CheckedState = canTrueColor ? CheckState.Checked : CheckState.UnChecked,
+            CanFocus = false,
+            Enabled = false,
+            Text = "SupportsTrueColor"
+        };
+        app.Add (cbSupportsTrueColor);
+
+        var cbUseTrueColor = new CheckBox
+        {
+            X = Pos.Right (cbSupportsTrueColor) + 1,
+            Y = Pos.Top (lblDriverName),
+            CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
+            Enabled = canTrueColor,
+            Text = "Force16Colors"
+        };
+        cbUseTrueColor.CheckedStateChanging += (_, evt) => { Application.Force16Colors = evt.Result == CheckState.Checked; };
+        app.Add (lblDriverName, cbSupportsTrueColor, cbUseTrueColor);
         // Set default colors.
         foregroundColorPicker.SelectedColor = _demoView.SuperView!.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ();
         backgroundColorPicker.SelectedColor = _demoView.SuperView.GetAttributeForRole (VisualRole.Normal).Background.GetClosestNamedColor16 ();

+ 14 - 3
Examples/UICatalog/Scenarios/CombiningMarks.cs

@@ -13,10 +13,11 @@ public class CombiningMarks : Scenario
         top.DrawComplete += (s, e) =>
         {
             // Forces reset _lineColsOffset because we're dealing with direct draw
-            Application.Current!.SetNeedsDraw ();
+            Application.TopRunnable!.SetNeedsDraw ();
 
             var i = -1;
-            top.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");
+            top.Move (0, ++i);
+            top.AddStr ("Terminal.Gui supports all combining sequences that can be rendered as an unique grapheme.");
             top.Move (0, ++i);
             top.AddStr ("\u0301<- \"\\u0301\" using AddStr.");
             top.Move (0, ++i);
@@ -38,7 +39,7 @@ public class CombiningMarks : Scenario
             top.AddRune ('\u0301');
             top.AddRune ('\u0328');
             top.AddRune (']');
-            top.AddStr ("<- \"[a\\u0301\\u0301\\u0328]\" using AddRune for each.");
+            top.AddStr ("<- \"[a\\u0301\\u0301\\u0328]\" using AddRune for each. Avoid use AddRune for combining sequences because may result with empty blocks at end.");
             top.Move (0, ++i);
             top.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u0301\\u0301\\u0328]\" using AddStr.");
             top.Move (0, ++i);
@@ -82,6 +83,16 @@ public class CombiningMarks : Scenario
             top.AddStr ("[\U0001F468\U0001F469\U0001F9D2]<- \"[\\U0001F468\\U0001F469\\U0001F9D2]\" using AddStr.");
             top.Move (0, ++i);
             top.AddStr ("[\U0001F468\u200D\U0001F469\u200D\U0001F9D2]<- \"[\\U0001F468\\u200D\\U0001F469\\u200D\\U0001F9D2]\" using AddStr.");
+            top.Move (0, ++i);
+            top.AddStr ("[\U0001F468\u200D\U0001F469\u200D\U0001F467\u200D\U0001F466]<- \"[\\U0001F468\\u200D\\U0001F469\\u200D\\U0001F467\\u200D\\U0001F466]\" using AddStr.");
+            top.Move (0, ++i);
+            top.AddStr ("[\u0e32\u0e33]<- \"[\\u0e32\\u0e33]\" using AddStr.");
+            top.Move (0, ++i);
+            top.AddStr ("[\U0001F469\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468]<- \"[\\U0001F469\\u200D\\u2764\\uFE0F\\u200D\\U0001F48B\\u200D\\U0001F468]\" using AddStr.");
+            top.Move (0, ++i);
+            top.AddStr ("[\u0061\uFE20\u0065\uFE21]<- \"[\\u0061\\uFE20\\u0065\\uFE21]\" using AddStr.");
+            top.Move (0, ++i);
+            top.AddStr ("[\u1100\uD7B0]<- \"[\\u1100\\uD7B0]\" using AddStr.");
         };
 
         Application.Run (top);

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

@@ -75,7 +75,7 @@ public class ConfigurationEditor : Scenario
 
         void ConfigurationManagerOnApplied (object? sender, ConfigurationManagerEventArgs e)
         {
-            Application.Current?.SetNeedsDraw ();
+            Application.TopRunnable?.SetNeedsDraw ();
         }
     }
     public void Save ()

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

@@ -502,7 +502,7 @@ public class CsvEditor : Scenario
             // Only set the current filename if we successfully loaded the entire file
             _currentFile = filename;
             _selectedCellTextField.SuperView.Enabled = true;
-            Application.Current.Title = $"{GetName ()} - {Path.GetFileName (_currentFile)}";
+            Application.TopRunnable.Title = $"{GetName ()} - {Path.GetFileName (_currentFile)}";
         }
         catch (Exception ex)
         {

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

@@ -284,7 +284,7 @@ public class DrawingArea : View
                     SetCurrentAttribute (c.Value.Value.Attribute ?? GetAttributeForRole (VisualRole.Normal));
 
                     // TODO: #2616 - Support combining sequences that don't normalize
-                    AddRune (c.Key.X, c.Key.Y, c.Value.Value.Rune);
+                    AddStr (c.Key.X, c.Key.Y, c.Value.Value.Grapheme);
                 }
             }
         }

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

@@ -171,7 +171,7 @@ public class Mazing : Scenario
                 if (_m.PlayerHp <= 0)
                 {
                     _message = "You died!";
-                    Application.Current!.SetNeedsDraw (); // trigger redraw
+                    Application.TopRunnable!.SetNeedsDraw (); // trigger redraw
                     _dead = true;
 
                     return; // Stop further action if dead
@@ -190,7 +190,7 @@ public class Mazing : Scenario
                 _message = string.Empty;
             }
 
-            Application.Current!.SetNeedsDraw (); // trigger redraw
+            Application.TopRunnable!.SetNeedsDraw (); // trigger redraw
         }
 
         // Optional win condition:
@@ -200,7 +200,7 @@ public class Mazing : Scenario
             _m = new (); // Generate a new maze
             _m.PlayerHp = hp;
             GenerateNpcs ();
-            Application.Current!.SetNeedsDraw (); // trigger redraw
+            Application.TopRunnable!.SetNeedsDraw (); // trigger redraw
         }
     }
 }

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

@@ -28,7 +28,7 @@ public class Shortcuts : Scenario
     private void App_Loaded (object? sender, EventArgs e)
     {
         Application.QuitKey = Key.F4.WithCtrl;
-        Application.Current!.Title = GetQuitKeyAndName ();
+        Application.TopRunnable!.Title = GetQuitKeyAndName ();
 
         ObservableCollection<string> eventSource = new ();
 
@@ -46,14 +46,14 @@ public class Shortcuts : Scenario
 
         eventLog.Width = Dim.Func (
                                    _ => Math.Min (
-                                                  Application.Current.Viewport.Width / 2,
+                                                  Application.TopRunnable.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));
-        Application.Current.Add (eventLog);
+        Application.TopRunnable.Add (eventLog);
 
         var alignKeysShortcut = new Shortcut
         {
@@ -86,7 +86,7 @@ public class Shortcuts : Scenario
                                                                           };
 
 
-        Application.Current.Add (alignKeysShortcut);
+        Application.TopRunnable.Add (alignKeysShortcut);
 
         var commandFirstShortcut = new Shortcut
         {
@@ -115,7 +115,7 @@ public class Shortcuts : Scenario
                                                                                                       $"{commandFirstShortcut.Id}.CommandView.CheckedStateChanging: {cb.Text}");
                                                                                      eventLog.MoveDown ();
 
-                                                                                     IEnumerable<View> toAlign = Application.Current.SubViews.OfType<Shortcut> ();
+                                                                                     IEnumerable<View> toAlign = Application.TopRunnable.SubViews.OfType<Shortcut> ();
                                                                                      IEnumerable<View> enumerable = toAlign as View [] ?? toAlign.ToArray ();
 
                                                                                      foreach (View view in enumerable)
@@ -134,7 +134,7 @@ public class Shortcuts : Scenario
                                                                                  }
                                                                              };
 
-        Application.Current.Add (commandFirstShortcut);
+        Application.TopRunnable.Add (commandFirstShortcut);
 
         var canFocusShortcut = new Shortcut
         {
@@ -159,7 +159,7 @@ public class Shortcuts : Scenario
                                                                                  SetCanFocus (e.Result == CheckState.Checked);
                                                                              }
                                                                          };
-        Application.Current.Add (canFocusShortcut);
+        Application.TopRunnable.Add (canFocusShortcut);
 
         var appShortcut = new Shortcut
         {
@@ -173,7 +173,7 @@ public class Shortcuts : Scenario
             BindKeyToApplication = true
         };
 
-        Application.Current.Add (appShortcut);
+        Application.TopRunnable.Add (appShortcut);
 
         var buttonShortcut = new Shortcut
         {
@@ -193,7 +193,7 @@ public class Shortcuts : Scenario
         var button = (Button)buttonShortcut.CommandView;
         buttonShortcut.Accepting += Button_Clicked;
 
-        Application.Current.Add (buttonShortcut);
+        Application.TopRunnable.Add (buttonShortcut);
 
         var optionSelectorShortcut = new Shortcut
         {
@@ -221,7 +221,7 @@ public class Shortcuts : Scenario
                                                                                     }
                                                                                 };
 
-        Application.Current.Add (optionSelectorShortcut);
+        Application.TopRunnable.Add (optionSelectorShortcut);
 
         var sliderShortcut = new Shortcut
         {
@@ -248,7 +248,7 @@ public class Shortcuts : Scenario
                                                                            eventLog.MoveDown ();
                                                                        };
 
-        Application.Current.Add (sliderShortcut);
+        Application.TopRunnable.Add (sliderShortcut);
 
         ListView listView = new ListView ()
         {
@@ -270,7 +270,7 @@ public class Shortcuts : Scenario
             Key = Key.F5.WithCtrl,
         };
 
-        Application.Current.Add (listViewShortcut);
+        Application.TopRunnable.Add (listViewShortcut);
 
         var noCommandShortcut = new Shortcut
         {
@@ -282,7 +282,7 @@ public class Shortcuts : Scenario
             Key = Key.D0
         };
 
-        Application.Current.Add (noCommandShortcut);
+        Application.TopRunnable.Add (noCommandShortcut);
 
         var noKeyShortcut = new Shortcut
         {
@@ -295,7 +295,7 @@ public class Shortcuts : Scenario
             HelpText = "Keyless"
         };
 
-        Application.Current.Add (noKeyShortcut);
+        Application.TopRunnable.Add (noKeyShortcut);
 
         var noHelpShortcut = new Shortcut
         {
@@ -308,7 +308,7 @@ public class Shortcuts : Scenario
             HelpText = ""
         };
 
-        Application.Current.Add (noHelpShortcut);
+        Application.TopRunnable.Add (noHelpShortcut);
         noHelpShortcut.SetFocus ();
 
         var framedShortcut = new Shortcut
@@ -340,7 +340,7 @@ public class Shortcuts : Scenario
         }
 
         framedShortcut.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Toplevel);
-        Application.Current.Add (framedShortcut);
+        Application.TopRunnable.Add (framedShortcut);
 
         // Horizontal
         var progressShortcut = new Shortcut
@@ -387,7 +387,7 @@ public class Shortcuts : Scenario
                          };
         timer.Start ();
 
-        Application.Current.Add (progressShortcut);
+        Application.TopRunnable.Add (progressShortcut);
 
         var textField = new TextField
         {
@@ -408,7 +408,7 @@ public class Shortcuts : Scenario
         };
         textField.CanFocus = true;
 
-        Application.Current.Add (textFieldShortcut);
+        Application.TopRunnable.Add (textFieldShortcut);
 
         var bgColorShortcut = new Shortcut
         {
@@ -450,19 +450,19 @@ public class Shortcuts : Scenario
                                         eventSource.Add ($"ColorChanged: {o.GetType ().Name} - {args.Result}");
                                         eventLog.MoveDown ();
 
-                                        Application.Current.SetScheme (
-                                                                   new (Application.Current.GetScheme ())
+                                        Application.TopRunnable.SetScheme (
+                                                                   new (Application.TopRunnable.GetScheme ())
                                                                    {
                                                                        Normal = new (
-                                                                                     Application.Current!.GetAttributeForRole (VisualRole.Normal).Foreground,
+                                                                                     Application.TopRunnable!.GetAttributeForRole (VisualRole.Normal).Foreground,
                                                                                      args.Result,
-                                                                                     Application.Current!.GetAttributeForRole (VisualRole.Normal).Style)
+                                                                                     Application.TopRunnable!.GetAttributeForRole (VisualRole.Normal).Style)
                                                                    });
                                     }
                                 };
         bgColorShortcut.CommandView = bgColor;
 
-        Application.Current.Add (bgColorShortcut);
+        Application.TopRunnable.Add (bgColorShortcut);
 
         var appQuitShortcut = new Shortcut
         {
@@ -476,9 +476,9 @@ public class Shortcuts : Scenario
         };
         appQuitShortcut.Accepting += (o, args) => { Application.RequestStop (); };
 
-        Application.Current.Add (appQuitShortcut);
+        Application.TopRunnable.Add (appQuitShortcut);
 
-        foreach (Shortcut shortcut in Application.Current.SubViews.OfType<Shortcut> ())
+        foreach (Shortcut shortcut in Application.TopRunnable.SubViews.OfType<Shortcut> ())
         {
             shortcut.Selecting += (o, args) =>
                                   {
@@ -529,7 +529,7 @@ public class Shortcuts : Scenario
 
         void SetCanFocus (bool canFocus)
         {
-            foreach (Shortcut peer in Application.Current!.SubViews.OfType<Shortcut> ())
+            foreach (Shortcut peer in Application.TopRunnable!.SubViews.OfType<Shortcut> ())
             {
                 if (peer.CanFocus)
                 {
@@ -542,7 +542,7 @@ public class Shortcuts : Scenario
         {
             var max = 0;
 
-            IEnumerable<Shortcut> toAlign = Application.Current!.SubViews.OfType<Shortcut> ().Where(s => !s.Y.Has<PosAnchorEnd>(out _)).Cast<Shortcut>();
+            IEnumerable<Shortcut> toAlign = Application.TopRunnable!.SubViews.OfType<Shortcut> ().Where(s => !s.Y.Has<PosAnchorEnd>(out _)).Cast<Shortcut>();
             IEnumerable<Shortcut> enumerable = toAlign as Shortcut [] ?? toAlign.ToArray ();
 
             if (align)

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

@@ -179,9 +179,9 @@ public class SingleBackgroundWorker : Scenario
 
                                                   var builderUI =
                                                       new StagingUIController (_startStaging, e.Result as ObservableCollection<string>);
-                                                  Toplevel top = Application.Current;
+                                                  Toplevel top = Application.TopRunnable;
                                                   top.Visible = false;
-                                                  Application.Current.Visible = false;
+                                                  Application.TopRunnable.Visible = false;
                                                   builderUI.Load ();
                                                   builderUI.Dispose ();
                                                   top.Visible = true;

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

@@ -86,17 +86,17 @@ public class Sliders : Scenario
                                 {
                                     if (single.Orientation == Orientation.Horizontal)
                                     {
-                                        single.Style.SpaceChar = new () { Rune = Glyphs.HLine };
-                                        single.Style.OptionChar = new () { Rune = Glyphs.HLine };
+                                        single.Style.SpaceChar = new () { Grapheme = Glyphs.HLine.ToString () };
+                                        single.Style.OptionChar = new () { Grapheme = Glyphs.HLine.ToString () };
                                     }
                                     else
                                     {
-                                        single.Style.SpaceChar = new () { Rune = Glyphs.VLine };
-                                        single.Style.OptionChar = new () { Rune = Glyphs.VLine };
+                                        single.Style.SpaceChar = new () { Grapheme = Glyphs.VLine.ToString () };
+                                        single.Style.OptionChar = new () { Grapheme = Glyphs.VLine.ToString () };
                                     }
                                 };
-        single.Style.SetChar = new () { Rune = Glyphs.ContinuousMeterSegment };
-        single.Style.DragChar = new () { Rune = Glyphs.ContinuousMeterSegment };
+        single.Style.SetChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () };
+        single.Style.DragChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () };
 
         v.Add (single);
 
@@ -257,7 +257,7 @@ public class Sliders : Scenario
                                                     {
                                                         s.Orientation = Orientation.Horizontal;
 
-                                                        s.Style.SpaceChar = new () { Rune = Glyphs.HLine };
+                                                        s.Style.SpaceChar = new () { Grapheme = Glyphs.HLine.ToString () };
 
                                                         if (prev == null)
                                                         {
@@ -275,7 +275,7 @@ public class Sliders : Scenario
                                                     {
                                                         s.Orientation = Orientation.Vertical;
 
-                                                        s.Style.SpaceChar = new () { Rune = Glyphs.VLine };
+                                                        s.Style.SpaceChar = new () { Grapheme = Glyphs.VLine.ToString () };
 
                                                         if (prev == null)
                                                         {

+ 4 - 7
Examples/UICatalog/Scenarios/SyntaxHighlighting.cs

@@ -152,12 +152,12 @@ public class SyntaxHighlighting : Scenario
                              ),
                          null,
                          new (
-                              "_Load Rune Cells",
+                              "_Load Text Cells",
                               "",
                               () => ApplyLoadCells ()
                              ),
                          new (
-                              "_Save Rune Cells",
+                              "_Save Text Cells",
                               "",
                               () => SaveCells ()
                              ),
@@ -240,12 +240,9 @@ public class SyntaxHighlighting : Scenario
         {
             string csName = color.Key;
 
-            foreach (Rune rune in csName.EnumerateRunes ())
-            {
-                cells.Add (new () { Rune = rune, Attribute = color.Value.Normal });
-            }
+            cells.AddRange (Cell.ToCellList (csName, color.Value.Normal));
 
-            cells.Add (new () { Rune = (Rune)'\n', Attribute = color.Value.Focus });
+            cells.Add (new () { Grapheme = "\n", Attribute = color.Value.Focus });
         }
 
         if (File.Exists (_path))

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

@@ -129,7 +129,7 @@ public sealed class Themes : Scenario
                                           {
                                               if (_view is { })
                                               {
-                                                  Application.Current!.SchemeName = args.NewValue;
+                                                  Application.TopRunnable!.SchemeName = args.NewValue;
 
                                                   if (_view.HasScheme)
                                                   {

+ 6 - 6
Examples/UICatalog/Scenarios/TreeUseCases.cs

@@ -77,7 +77,7 @@ public class TreeUseCases : Scenario
 
         if (_currentTree != null)
         {
-            Application.Current.Remove (_currentTree);
+            Application.TopRunnable.Remove (_currentTree);
             _currentTree.Dispose ();
         }
 
@@ -97,7 +97,7 @@ public class TreeUseCases : Scenario
             tree.TreeBuilder = new GameObjectTreeBuilder ();
         }
 
-        Application.Current.Add (tree);
+        Application.TopRunnable.Add (tree);
 
         tree.AddObject (army1);
 
@@ -117,13 +117,13 @@ public class TreeUseCases : Scenario
 
         if (_currentTree != null)
         {
-            Application.Current.Remove (_currentTree);
+            Application.TopRunnable.Remove (_currentTree);
             _currentTree.Dispose ();
         }
 
         var tree = new TreeView { X = 0, Y = 1, Width = Dim.Fill(), Height = Dim.Fill (1) };
 
-        Application.Current.Add (tree);
+        Application.TopRunnable.Add (tree);
 
         tree.AddObject (myHouse);
 
@@ -134,13 +134,13 @@ public class TreeUseCases : Scenario
     {
         if (_currentTree != null)
         {
-            Application.Current.Remove (_currentTree);
+            Application.TopRunnable.Remove (_currentTree);
             _currentTree.Dispose ();
         }
 
         var tree = new TreeView { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) };
 
-        Application.Current.Add (tree);
+        Application.TopRunnable.Add (tree);
 
         var root1 = new TreeNode ("Root1");
         root1.Children.Add (new TreeNode ("Child1.1"));

+ 0 - 157
Examples/UICatalog/Scenarios/TrueColors.cs

@@ -1,157 +0,0 @@
-using System;
-
-namespace UICatalog.Scenarios;
-
-[ScenarioMetadata ("True Colors", "Demonstration of true color support.")]
-[ScenarioCategory ("Colors")]
-public class TrueColors : Scenario
-{
-    public override void Main ()
-    {
-        Application.Init ();
-
-        Window app = new ()
-        {
-            Title = GetQuitKeyAndName ()
-        };
-
-        var x = 2;
-        var y = 1;
-
-        bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false;
-
-        var lblDriverName = new Label
-        {
-            X = x, Y = y++, Text = $"Current driver is {Application.Driver?.GetType ().Name}"
-        };
-        app.Add (lblDriverName);
-        y++;
-
-        var cbSupportsTrueColor = new CheckBox
-        {
-            X = x,
-            Y = y++,
-            CheckedState = canTrueColor ? CheckState.Checked : CheckState.UnChecked,
-            CanFocus = false,
-            Enabled = false,
-            Text = "Driver supports true color "
-        };
-        app.Add (cbSupportsTrueColor);
-
-        var cbUseTrueColor = new CheckBox
-        {
-            X = x,
-            Y = y++,
-            CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
-            Enabled = canTrueColor,
-            Text = "Force 16 colors"
-        };
-        cbUseTrueColor.CheckedStateChanging += (_, evt) => { Application.Force16Colors = evt.Result == CheckState.Checked; };
-        app.Add (cbUseTrueColor);
-
-        y += 2;
-        SetupGradient ("Red gradient", x, ref y, i => new (i, 0));
-        SetupGradient ("Green gradient", x, ref y, i => new (0, i));
-        SetupGradient ("Blue gradient", x, ref y, i => new (0, 0, i));
-        SetupGradient ("Yellow gradient", x, ref y, i => new (i, i));
-        SetupGradient ("Magenta gradient", x, ref y, i => new (i, 0, i));
-        SetupGradient ("Cyan gradient", x, ref y, i => new (0, i, i));
-        SetupGradient ("Gray gradient", x, ref y, i => new (i, i, i));
-
-        app.Add (
-                 new Label { X = Pos.AnchorEnd (44), Y = 2, Text = "Mouse over to get the gradient view color:" }
-                );
-
-        app.Add (
-                 new Label { X = Pos.AnchorEnd (44), Y = 4, Text = "Red:" }
-                );
-
-        app.Add (
-                 new Label { X = Pos.AnchorEnd (44), Y = 5, Text = "Green:" }
-                );
-
-        app.Add (
-                 new Label { X = Pos.AnchorEnd (44), Y = 6, Text = "Blue:" }
-                );
-
-        app.Add (
-                 new Label { X = Pos.AnchorEnd (44), Y = 8, Text = "Darker:" }
-                );
-
-        app.Add (
-                 new Label { X = Pos.AnchorEnd (44), Y = 9, Text = "Lighter:" }
-                );
-
-        var lblRed = new Label { X = Pos.AnchorEnd (32), Y = 4, Text = "na" };
-        app.Add (lblRed);
-        var lblGreen = new Label { X = Pos.AnchorEnd (32), Y = 5, Text = "na" };
-        app.Add (lblGreen);
-        var lblBlue = new Label { X = Pos.AnchorEnd (32), Y = 6, Text = "na" };
-        app.Add (lblBlue);
-
-        var lblDarker = new Label { X = Pos.AnchorEnd (32), Y = 8, Text = "     " };
-        app.Add (lblDarker);
-
-        var lblLighter = new Label { X = Pos.AnchorEnd (32), Y = 9, Text = "    " };
-        app.Add (lblLighter);
-
-        Application.MouseEvent += (s, e) =>
-                                  {
-                                      if (e.View == null)
-                                      {
-                                          return;
-                                      }
-
-                                      if (e.Flags == MouseFlags.Button1Clicked)
-                                      {
-                                          Attribute normal = e.View.GetAttributeForRole (VisualRole.Normal);
-
-                                          lblLighter.SetScheme (new (e.View.GetScheme ())
-                                          {
-                                              Normal = new (
-                                                            normal.Foreground,
-                                                            normal.Background.GetBrighterColor ()
-                                                           )
-                                          });
-                                      }
-                                      else
-                                      {
-                                          Attribute normal = e.View.GetAttributeForRole (VisualRole.Normal);
-                                          lblRed.Text = normal.Foreground.R.ToString ();
-                                          lblGreen.Text = normal.Foreground.G.ToString ();
-                                          lblBlue.Text = normal.Foreground.B.ToString ();
-                                      }
-                                  };
-        Application.Run (app);
-        app.Dispose ();
-
-        Application.Shutdown ();
-
-        return;
-
-        void SetupGradient (string name, int x, ref int y, Func<int, Color> colorFunc)
-        {
-            var gradient = new Label { X = x, Y = y++, Text = name };
-            app.Add (gradient);
-
-            for (int dx = x, i = 0; i <= 256; i += 4)
-            {
-                var l = new Label
-                {
-                    X = dx++,
-                    Y = y
-                };
-                l.SetScheme (new ()
-                {
-                    Normal = new (
-                                  colorFunc (Math.Clamp (i, 0, 255)),
-                                  colorFunc (Math.Clamp (i, 0, 255))
-                                 )
-                });
-                app.Add (l);
-            }
-
-            y += 2;
-        }
-    }
-}

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

@@ -69,7 +69,7 @@ public class WindowsAndFrameViews : Scenario
         // add it to our list
         listWin.Add (win);
 
-        // create 3 more Windows in a loop, adding them Application.Current
+        // create 3 more Windows in a loop, adding them Application.TopRunnable
         // Each with a
         //	button
         //  sub Window with

+ 2 - 2
Examples/UICatalog/UICatalog.cs

@@ -246,7 +246,7 @@ public class UICatalog
 
     /// <summary>
     ///     Shows the UI Catalog selection UI. When the user selects a Scenario to run, the UI Catalog main app UI is
-    ///     killed and the Scenario is run as though it were Application.Current. When the Scenario exits, this function exits.
+    ///     killed and the Scenario is run as though it were Application.TopRunnable. When the Scenario exits, this function exits.
     /// </summary>
     /// <returns></returns>
     private static Scenario RunUICatalogTopLevel ()
@@ -347,7 +347,7 @@ public class UICatalog
 
     private static void ConfigFileChanged (object sender, FileSystemEventArgs e)
     {
-        if (Application.Current == null)
+        if (Application.TopRunnable == null)
         {
             return;
         }

+ 1 - 1
Examples/UICatalog/UICatalogTop.cs

@@ -700,7 +700,7 @@ public class UICatalogTop : Toplevel
         _disableMouseCb!.CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.UnChecked;
         _force16ColorsShortcutCb!.CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked;
 
-        Application.Current?.SetNeedsDraw ();
+        Application.TopRunnable?.SetNeedsDraw ();
     }
 
     private void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a) { ConfigApplied (); }

+ 5 - 5
Terminal.Gui/App/Application.Current.cs

@@ -7,12 +7,12 @@ public static partial class Application // Current handling
     /// <inheritdoc cref="IApplication.SessionStack"/>
     [Obsolete ("The legacy static Application object is going away.")] public static ConcurrentStack<Toplevel> SessionStack => ApplicationImpl.Instance.SessionStack;
 
-    /// <summary>The <see cref="Toplevel"/> that is currently active.</summary>
-    /// <value>The current toplevel.</value>
+    /// <summary>The <see cref="Toplevel"/> that is on the top of the <see cref="SessionStack"/>.</summary>
+    /// <value>The top runnable.</value>
     [Obsolete ("The legacy static Application object is going away.")]
-    public static Toplevel? Current
+    public static Toplevel? TopRunnable
     {
-        get => ApplicationImpl.Instance.Current;
-        internal set => ApplicationImpl.Instance.Current = value;
+        get => ApplicationImpl.Instance.TopRunnable;
+        internal set => ApplicationImpl.Instance.TopRunnable = value;
     }
 }

+ 5 - 11
Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

@@ -145,13 +145,9 @@ public partial class ApplicationImpl
     }
 #endif
 
-    private bool _isResetingState;
-
     /// <inheritdoc/>
     public void ResetState (bool ignoreDisposed = false)
     {
-        _isResetingState = true;
-
         // Shutdown is the bookend for Init. As such it needs to clean up all resources
         // Init created. Apps that do any threading will need to code defensively for this.
         // e.g. see Issue #537
@@ -182,21 +178,21 @@ public partial class ApplicationImpl
 
 #if DEBUG_IDISPOSABLE
 
-        // Don't dispose the Current. It's up to caller dispose it
-        if (View.EnableDebugIDisposableAsserts && !ignoreDisposed && Current is { })
+        // Don't dispose the TopRunnable. It's up to caller dispose it
+        if (View.EnableDebugIDisposableAsserts && !ignoreDisposed && TopRunnable is { })
         {
-            Debug.Assert (Current.WasDisposed, $"Title = {Current.Title}, Id = {Current.Id}");
+            Debug.Assert (TopRunnable.WasDisposed, $"Title = {TopRunnable.Title}, Id = {TopRunnable.Id}");
 
             // If End wasn't called _CachedSessionTokenToplevel may be null
             if (CachedSessionTokenToplevel is { })
             {
                 Debug.Assert (CachedSessionTokenToplevel.WasDisposed);
-                Debug.Assert (CachedSessionTokenToplevel == Current);
+                Debug.Assert (CachedSessionTokenToplevel == TopRunnable);
             }
         }
 #endif
 
-        Current = null;
+        TopRunnable = null;
         CachedSessionTokenToplevel = null;
 
         // === 4. Clean up driver ===
@@ -250,8 +246,6 @@ public partial class ApplicationImpl
         // gui.cs does no longer process any callbacks. See #1084 for more details:
         // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
         SynchronizationContext.SetSynchronizationContext (null);
-
-        _isResetingState = false;
     }
 
     /// <summary>

+ 32 - 32
Terminal.Gui/App/ApplicationImpl.Run.cs

@@ -36,28 +36,28 @@ public partial class ApplicationImpl
         var rs = new SessionToken (toplevel);
 
 #if DEBUG_IDISPOSABLE
-        if (View.EnableDebugIDisposableAsserts && Current is { } && toplevel != Current && !SessionStack.Contains (Current))
+        if (View.EnableDebugIDisposableAsserts && TopRunnable is { } && toplevel != TopRunnable && !SessionStack.Contains (TopRunnable))
         {
-            // This assertion confirm if the Current was already disposed
-            Debug.Assert (Current.WasDisposed);
-            Debug.Assert (Current == CachedSessionTokenToplevel);
+            // This assertion confirm if the TopRunnable was already disposed
+            Debug.Assert (TopRunnable.WasDisposed);
+            Debug.Assert (TopRunnable == CachedSessionTokenToplevel);
         }
 #endif
 
         lock (SessionStack)
         {
-            if (Current is { } && toplevel != Current && !SessionStack.Contains (Current))
+            if (TopRunnable is { } && toplevel != TopRunnable && !SessionStack.Contains (TopRunnable))
             {
-                // If Current was already disposed and isn't on the Toplevels Stack,
+                // If TopRunnable was already disposed and isn't on the Toplevels Stack,
                 // clean it up here if is the same as _CachedSessionTokenToplevel
-                if (Current == CachedSessionTokenToplevel)
+                if (TopRunnable == CachedSessionTokenToplevel)
                 {
-                    Current = null;
+                    TopRunnable = null;
                 }
                 else
                 {
                     // Probably this will never hit
-                    throw new ObjectDisposedException (Current.GetType ().FullName);
+                    throw new ObjectDisposedException (TopRunnable.GetType ().FullName);
                 }
             }
 
@@ -89,35 +89,35 @@ public partial class ApplicationImpl
             }
         }
 
-        if (Current is null)
+        if (TopRunnable is null)
         {
             toplevel.App = this;
-            Current = toplevel;
+            TopRunnable = toplevel;
         }
 
-        if ((Current?.Modal == false && toplevel.Modal)
-            || (Current?.Modal == false && !toplevel.Modal)
-            || (Current?.Modal == true && toplevel.Modal))
+        if ((TopRunnable?.Modal == false && toplevel.Modal)
+            || (TopRunnable?.Modal == false && !toplevel.Modal)
+            || (TopRunnable?.Modal == true && toplevel.Modal))
         {
             if (toplevel.Visible)
             {
-                if (Current is { HasFocus: true })
+                if (TopRunnable is { HasFocus: true })
                 {
-                    Current.HasFocus = false;
+                    TopRunnable.HasFocus = false;
                 }
 
-                // Force leave events for any entered views in the old Current
+                // Force leave events for any entered views in the old TopRunnable
                 if (Mouse.LastMousePosition is { })
                 {
                     Mouse.RaiseMouseEnterLeaveEvents (Mouse.LastMousePosition!.Value, new ());
                 }
 
-                Current?.OnDeactivate (toplevel);
-                Toplevel previousTop = Current!;
+                TopRunnable?.OnDeactivate (toplevel);
+                Toplevel previousTop = TopRunnable!;
 
-                Current = toplevel;
-                Current.App = this;
-                Current.OnActivate (previousTop);
+                TopRunnable = toplevel;
+                TopRunnable.App = this;
+                TopRunnable.OnActivate (previousTop);
             }
         }
 
@@ -194,11 +194,11 @@ public partial class ApplicationImpl
             throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view");
         }
 
-        Current = view;
+        TopRunnable = view;
 
         SessionToken rs = Begin (view);
 
-        Current.Running = true;
+        TopRunnable.Running = true;
 
         var firstIteration = true;
 
@@ -254,8 +254,8 @@ public partial class ApplicationImpl
         if (SessionStack.TryPeek (out Toplevel? newTop))
         {
             newTop.App = this;
-            Current = newTop;
-            Current?.SetNeedsDraw ();
+            TopRunnable = newTop;
+            TopRunnable?.SetNeedsDraw ();
         }
 
         if (sessionToken.Toplevel is { HasFocus: true })
@@ -263,9 +263,9 @@ public partial class ApplicationImpl
             sessionToken.Toplevel.HasFocus = false;
         }
 
-        if (Current is { HasFocus: false })
+        if (TopRunnable is { HasFocus: false })
         {
-            Current.SetFocus ();
+            TopRunnable.SetFocus ();
         }
 
         CachedSessionTokenToplevel = sessionToken.Toplevel;
@@ -285,9 +285,9 @@ public partial class ApplicationImpl
     /// <inheritdoc/>
     public void RequestStop (Toplevel? top)
     {
-        Logging.Trace ($"Current: '{(top is { } ? top : "null")}'");
+        Logging.Trace ($"TopRunnable: '{(top is { } ? top : "null")}'");
 
-        top ??= Current;
+        top ??= TopRunnable;
 
         if (top == null)
         {
@@ -327,7 +327,7 @@ public partial class ApplicationImpl
     public void Invoke (Action<IApplication>? action)
     {
         // If we are already on the main UI thread
-        if (Current is { Running: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId)
+        if (TopRunnable is { Running: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId)
         {
             action?.Invoke (this);
 
@@ -350,7 +350,7 @@ public partial class ApplicationImpl
     public void Invoke (Action action)
     {
         // If we are already on the main UI thread
-        if (Current is { Running: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId)
+        if (TopRunnable is { Running: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId)
         {
             action?.Invoke ();
 

+ 6 - 6
Terminal.Gui/App/ApplicationImpl.cs

@@ -121,19 +121,19 @@ public partial class ApplicationImpl : IApplication
         set => _navigation = value ?? throw new ArgumentNullException (nameof (value));
     }
 
-    private Toplevel? _current;
+    private Toplevel? _topRunnable;
 
     /// <inheritdoc/>
-    public Toplevel? Current
+    public Toplevel? TopRunnable
     {
-        get => _current;
+        get => _topRunnable;
         set
         {
-            _current = value;
+            _topRunnable = value;
 
-            if (_current is { })
+            if (_topRunnable is { })
             {
-                _current.App = this;
+                _topRunnable.App = this;
             }
         }
     }

+ 1 - 1
Terminal.Gui/App/ApplicationNavigation.cs

@@ -113,6 +113,6 @@ public class ApplicationNavigation
         {
             return visiblePopover.AdvanceFocus (direction, behavior);
         }
-        return App?.Current is { } && App.Current.AdvanceFocus (direction, behavior);
+        return App?.TopRunnable is { } && App.TopRunnable.AdvanceFocus (direction, behavior);
     }
 }

+ 4 - 4
Terminal.Gui/App/ApplicationPopover.cs

@@ -41,8 +41,8 @@ public sealed class ApplicationPopover : IDisposable
     {
         if (popover is { } && !IsRegistered (popover))
         {
-            // When created, set IPopover.Toplevel to the current Application.Current
-            popover.Current ??= App?.Current;
+            // When created, set IPopover.Toplevel to the current Application.TopRunnable
+            popover.Current ??= App?.TopRunnable;
 
             if (popover is View popoverView)
             {
@@ -166,7 +166,7 @@ public sealed class ApplicationPopover : IDisposable
         {
             _activePopover = null;
             popoverView.Visible = false;
-            popoverView.App?.Current?.SetNeedsDraw ();
+            popoverView.App?.TopRunnable?.SetNeedsDraw ();
         }
     }
 
@@ -215,7 +215,7 @@ public sealed class ApplicationPopover : IDisposable
         {
             if (popover == activePopover
                 || popover is not View popoverView
-                || (popover.Current is { } && popover.Current != App?.Current))
+                || (popover.Current is { } && popover.Current != App?.TopRunnable))
             {
                 continue;
             }

+ 6 - 5
Terminal.Gui/App/IApplication.cs

@@ -301,14 +301,14 @@ public interface IApplication
     /// <remarks>
     ///     <para>This will cause <see cref="Run(Toplevel, Func{Exception, bool})"/> to return.</para>
     ///     <para>
-    ///         This is equivalent to calling <see cref="RequestStop(Toplevel)"/> with <see cref="Current"/> as the parameter.
+    ///         This is equivalent to calling <see cref="RequestStop(Toplevel)"/> with <see cref="TopRunnable"/> as the parameter.
     ///     </para>
     /// </remarks>
     void RequestStop ();
 
     /// <summary>Requests that the currently running Session stop. The Session will stop after the current iteration completes.</summary>
     /// <param name="top">
-    ///     The <see cref="Toplevel"/> to stop. If <see langword="null"/>, stops the currently running <see cref="Current"/>.
+    ///     The <see cref="Toplevel"/> to stop. If <see langword="null"/>, stops the currently running <see cref="TopRunnable"/>.
     /// </param>
     /// <remarks>
     ///     <para>This will cause <see cref="Run(Toplevel, Func{Exception, bool})"/> to return.</para>
@@ -356,13 +356,14 @@ public interface IApplication
 
     #region Toplevel Management
 
-    /// <summary>Gets or sets the currently active Toplevel.</summary>
+    /// <summary>Gets or sets the Toplevel that is on the top of the <see cref="SessionStack"/>.</summary>
     /// <remarks>
     ///     <para>
+    ///         The top runnable in the session stack captures all mouse and keyboard input.
     ///         This is set by <see cref="Begin(Toplevel)"/> and cleared by <see cref="End(SessionToken)"/>.
     ///     </para>
     /// </remarks>
-    Toplevel? Current { get; set; }
+    Toplevel? TopRunnable { get; set; }
 
     /// <summary>Gets the stack of all active Toplevel sessions.</summary>
     /// <remarks>
@@ -433,7 +434,7 @@ public interface IApplication
     /// <remarks>
     ///     <para>
     ///         This is typically set to <see langword="true"/> when a View's <see cref="View.Frame"/> changes and that view
-    ///         has no SuperView (e.g. when <see cref="Current"/> is moved or resized).
+    ///         has no SuperView (e.g. when <see cref="TopRunnable"/> is moved or resized).
     ///     </para>
     ///     <para>
     ///         Automatically reset to <see langword="false"/> after <see cref="LayoutAndDraw"/> processes it.

+ 1 - 1
Terminal.Gui/App/IPopover.cs

@@ -55,7 +55,7 @@ public interface IPopover
     ///     events from the <see cref="IApplication"/>. If set, it will only receive keyboard events the Toplevel would normally
     ///     receive.
     ///     When <see cref="ApplicationPopover.Register"/> is called, the <see cref="Current"/> is set to the current
-    ///     <see cref="IApplication.Current"/> if not already set.
+    ///     <see cref="IApplication.TopRunnable"/> if not already set.
     /// </summary>
     Toplevel? Current { get; set; }
 }

+ 2 - 2
Terminal.Gui/App/Keyboard/KeyboardImpl.cs

@@ -138,7 +138,7 @@ internal class KeyboardImpl : IKeyboard
             return true;
         }
 
-        if (App?.Current is null)
+        if (App?.TopRunnable is null)
         {
             if (App?.SessionStack is { })
             {
@@ -158,7 +158,7 @@ internal class KeyboardImpl : IKeyboard
         }
         else
         {
-            if (App.Current.NewKeyDownEvent (key))
+            if (App.TopRunnable.NewKeyDownEvent (key))
             {
                 return true;
             }

+ 4 - 4
Terminal.Gui/App/MainLoop/ApplicationMainLoop.cs

@@ -82,7 +82,7 @@ public class ApplicationMainLoop<TInputRecord> : IApplicationMainLoop<TInputReco
     }
 
     /// <summary>
-    ///     Handles raising events and setting required draw status etc when <see cref="IApplication.Current"/> changes
+    ///     Handles raising events and setting required draw status etc when <see cref="IApplication.TopRunnable"/> changes
     /// </summary>
     public IToplevelTransitionManager ToplevelTransitionManager = new ToplevelTransitionManager ();
 
@@ -145,10 +145,10 @@ public class ApplicationMainLoop<TInputRecord> : IApplicationMainLoop<TInputReco
         ToplevelTransitionManager.RaiseReadyEventIfNeeded (App);
         ToplevelTransitionManager.HandleTopMaybeChanging (App);
 
-        if (App?.Current != null)
+        if (App?.TopRunnable != null)
         {
             bool needsDrawOrLayout = AnySubViewsNeedDrawn (App?.Popover?.GetActivePopover () as View)
-                                     || AnySubViewsNeedDrawn (App?.Current)
+                                     || AnySubViewsNeedDrawn (App?.TopRunnable)
                                      || (App?.Mouse.MouseGrabView != null && AnySubViewsNeedDrawn (App?.Mouse.MouseGrabView));
 
             bool sizeChanged = SizeMonitor.Poll ();
@@ -176,7 +176,7 @@ public class ApplicationMainLoop<TInputRecord> : IApplicationMainLoop<TInputReco
 
     private void SetCursor ()
     {
-        View? mostFocused = App?.Current!.MostFocused;
+        View? mostFocused = App?.TopRunnable!.MostFocused;
 
         if (mostFocused == null)
         {

+ 3 - 3
Terminal.Gui/App/Mouse/MouseImpl.cs

@@ -67,7 +67,7 @@ internal class MouseImpl : IMouse
         //Debug.Assert (mouseEvent.Position == mouseEvent.ScreenPosition);
         mouseEvent.Position = mouseEvent.ScreenPosition;
 
-        List<View?>? currentViewsUnderMouse = App?.Current?.GetViewsUnderLocation (mouseEvent.ScreenPosition, ViewportSettingsFlags.TransparentMouse);
+        List<View?>? currentViewsUnderMouse = App?.TopRunnable?.GetViewsUnderLocation (mouseEvent.ScreenPosition, ViewportSettingsFlags.TransparentMouse);
 
         View? deepestViewUnderMouse = currentViewsUnderMouse?.LastOrDefault ();
 
@@ -114,9 +114,9 @@ internal class MouseImpl : IMouse
             return;
         }
 
-        // if the mouse is outside the Application.Current or Popover hierarchy, we don't want to
+        // if the mouse is outside the Application.TopRunnable or Popover hierarchy, we don't want to
         // send the mouse event to the deepest view under the mouse.
-        if (!View.IsInHierarchy (App?.Current, deepestViewUnderMouse, true) && !View.IsInHierarchy (App?.Popover?.GetActivePopover () as View, deepestViewUnderMouse, true))
+        if (!View.IsInHierarchy (App?.TopRunnable, deepestViewUnderMouse, true) && !View.IsInHierarchy (App?.Popover?.GetActivePopover () as View, deepestViewUnderMouse, true))
         {
             return;
         }

+ 1 - 1
Terminal.Gui/App/PopoverBaseImpl.cs

@@ -119,7 +119,7 @@ public abstract class PopoverBaseImpl : View, IPopover
             // Whenever visible is changing to false, we need to reset the focus
             if (ApplicationNavigation.IsInHierarchy (this, App?.Navigation?.GetFocused ()))
             {
-                App?.Navigation?.SetFocused (App?.Current?.MostFocused);
+                App?.Navigation?.SetFocused (App?.TopRunnable?.MostFocused);
             }
         }
 

+ 3 - 3
Terminal.Gui/App/Toplevel/ToplevelTransitionManager.cs

@@ -13,7 +13,7 @@ public class ToplevelTransitionManager : IToplevelTransitionManager
     /// <inheritdoc/>
     public void RaiseReadyEventIfNeeded (IApplication? app)
     {
-        Toplevel? top = app?.Current;
+        Toplevel? top = app?.TopRunnable;
 
         if (top != null && !_readiedTopLevels.Contains (top))
         {
@@ -29,13 +29,13 @@ public class ToplevelTransitionManager : IToplevelTransitionManager
     /// <inheritdoc/>
     public void HandleTopMaybeChanging (IApplication? app)
     {
-        Toplevel? newTop = app?.Current;
+        Toplevel? newTop = app?.TopRunnable;
 
         if (_lastTop != null && _lastTop != newTop && newTop != null)
         {
             newTop.SetNeedsDraw ();
         }
 
-        _lastTop = app?.Current;
+        _lastTop = app?.TopRunnable;
     }
 }

+ 86 - 70
Terminal.Gui/Drawing/Cell.cs

@@ -1,80 +1,108 @@
 
-
 namespace Terminal.Gui.Drawing;
 
 /// <summary>
 ///     Represents a single row/column in a Terminal.Gui rendering surface (e.g. <see cref="LineCanvas"/> and
 ///     <see cref="IDriver"/>).
 /// </summary>
-public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Rune Rune = default)
+public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, string Grapheme = "")
 {
     /// <summary>The attributes to use when drawing the Glyph.</summary>
     public Attribute? Attribute { get; set; } = Attribute;
 
     /// <summary>
-    ///     Gets or sets a value indicating whether this <see cref="T:Terminal.Gui.Cell"/> has been modified since the
+    ///     Gets or sets a value indicating whether this <see cref="T:Terminal.Gui.Drawing.Cell"/> has been modified since the
     ///     last time it was drawn.
     /// </summary>
     public bool IsDirty { get; set; } = IsDirty;
 
-    private Rune _rune = Rune;
+    private string _grapheme = Grapheme;
 
-    /// <summary>The character to display. If <see cref="Rune"/> is <see langword="null"/>, then <see cref="Rune"/> is ignored.</summary>
-    public Rune Rune
+    /// <summary>
+    ///     The single grapheme cluster to display from this cell. If <see cref="Grapheme"/> is <see langword="null"/> or
+    ///     <see cref="string.Empty"/>, then <see cref="Cell"/> is ignored.
+    /// </summary>
+    public string Grapheme
     {
-        get => _rune;
+        readonly get => _grapheme;
         set
         {
-            _combiningMarks?.Clear ();
-            _rune = value;
-        }
-    }
+            if (GraphemeHelper.GetGraphemes(value).ToArray().Length > 1)
+            {
+                throw new InvalidOperationException ($"Only a single {nameof (Grapheme)} cluster is allowed per Cell.");
+            }
 
-    private List<Rune>? _combiningMarks;
+            if (!string.IsNullOrEmpty (value) && value.Length == 1 && char.IsSurrogate (value [0]))
+            {
+                throw new ArgumentException ($"Only valid Unicode scalar values are allowed in a single {nameof (Grapheme)} cluster.");
+            }
 
-    /// <summary>
-    ///     The combining marks for <see cref="Rune"/> that when combined makes this Cell a combining sequence. If
-    ///     <see cref="CombiningMarks"/> empty, then <see cref="CombiningMarks"/> is ignored.
-    /// </summary>
-    /// <remarks>
-    ///     Only valid in the rare case where <see cref="Rune"/> is a combining sequence that could not be normalized to a
-    ///     single Rune.
-    /// </remarks>
-    internal IReadOnlyList<Rune> CombiningMarks
-    {
-        // PERFORMANCE: Downside of the interface return type is that List<T> struct enumerator cannot be utilized, i.e. enumerator is allocated.
-        // If enumeration is used heavily in the future then might be better to expose the List<T> Enumerator directly via separate mechanism.
-        get
-        {
-            // Avoid unnecessary list allocation.
-            if (_combiningMarks == null)
+            try
             {
-                return Array.Empty<Rune> ();
+                _grapheme = !string.IsNullOrEmpty (value) && !value.IsNormalized (NormalizationForm.FormC)
+                                ? value.Normalize (NormalizationForm.FormC)
+                                : value;
+            }
+            catch (ArgumentException)
+            {
+                // leave text unnormalized
+                _grapheme = value;
             }
-            return _combiningMarks;
         }
     }
 
     /// <summary>
-    ///     Adds combining mark to the cell.
+    ///     The rune for <see cref="Grapheme"/> or runes for <see cref="Grapheme"/> that when combined makes this Cell a combining sequence.
     /// </summary>
-    /// <param name="combiningMark">The combining mark to add to the cell.</param>
-    internal void AddCombiningMark (Rune combiningMark)
+    /// <remarks>
+    ///     In the case where <see cref="Grapheme"/> has more than one rune it is a combining sequence that is normalized to a
+    ///     single Text which may occupies 1 or 2 columns.
+    /// </remarks>
+    public IReadOnlyList<Rune> Runes => string.IsNullOrEmpty (Grapheme) ? [] : Grapheme.EnumerateRunes ().ToList ();
+
+    /// <inheritdoc/>
+    public override string ToString ()
     {
-        _combiningMarks ??= [];
-        _combiningMarks.Add (combiningMark);
+        string visibleText = EscapeControlAndInvisible (Grapheme);
+
+        return $"[\"{visibleText}\":{Attribute}]";
     }
 
-    /// <summary>
-    ///     Clears combining marks of the cell.
-    /// </summary>
-    internal void ClearCombiningMarks ()
+    private static string EscapeControlAndInvisible (string text)
     {
-        _combiningMarks?.Clear ();
-    }
+        if (string.IsNullOrEmpty (text))
+        {
+            return "";
+        }
 
-    /// <inheritdoc/>
-    public override string ToString () { return $"['{Rune}':{Attribute}]"; }
+        var sb = new StringBuilder ();
+
+        foreach (var rune in text.EnumerateRunes ())
+        {
+            switch (rune.Value)
+            {
+                case '\0': sb.Append ("␀"); break;
+                case '\t': sb.Append ("\\t"); break;
+                case '\r': sb.Append ("\\r"); break;
+                case '\n': sb.Append ("\\n"); break;
+                case '\f': sb.Append ("\\f"); break;
+                case '\v': sb.Append ("\\v"); break;
+                default:
+                    if (char.IsControl ((char)rune.Value))
+                    {
+                        // show as \uXXXX
+                        sb.Append ($"\\u{rune.Value:X4}");
+                    }
+                    else
+                    {
+                        sb.Append (rune);
+                    }
+                    break;
+            }
+        }
+
+        return sb.ToString ();
+    }
 
     /// <summary>Converts the string into a <see cref="List{Cell}"/>.</summary>
     /// <param name="str">The string to convert.</param>
@@ -82,12 +110,8 @@ public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Ru
     /// <returns></returns>
     public static List<Cell> ToCellList (string str, Attribute? attribute = null)
     {
-        List<Cell> cells = new ();
-
-        foreach (Rune rune in str.EnumerateRunes ())
-        {
-            cells.Add (new () { Rune = rune, Attribute = attribute });
-        }
+        List<Cell> cells = [];
+        cells.AddRange (GraphemeHelper.GetGraphemes (str).Select (grapheme => new Cell { Grapheme = grapheme, Attribute = attribute }));
 
         return cells;
     }
@@ -100,9 +124,7 @@ public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Ru
     /// <returns>A <see cref="List{Cell}"/> for each line.</returns>
     public static List<List<Cell>> StringToLinesOfCells (string content, Attribute? attribute = null)
     {
-        List<Cell> cells = content.EnumerateRunes ()
-                                  .Select (x => new Cell { Rune = x, Attribute = attribute })
-                                  .ToList ();
+        List<Cell> cells = ToCellList (content, attribute);
 
         return SplitNewLines (cells);
     }
@@ -112,14 +134,14 @@ public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Ru
     /// <returns></returns>
     public static string ToString (IEnumerable<Cell> cells)
     {
-        var str = string.Empty;
+        StringBuilder sb = new ();
 
         foreach (Cell cell in cells)
         {
-            str += cell.Rune.ToString ();
+            sb.Append (cell.Grapheme);
         }
 
-        return str;
+        return sb.ToString ();
     }
 
     /// <summary>Converts a <see cref="List{Cell}"/> generic collection into a string.</summary>
@@ -147,26 +169,19 @@ public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Ru
 
     internal static List<Cell> StringToCells (string str, Attribute? attribute = null)
     {
-        List<Cell> cells = [];
-
-        foreach (Rune rune in str.ToRunes ())
-        {
-            cells.Add (new () { Rune = rune, Attribute = attribute });
-        }
-
-        return cells;
+        return ToCellList (str, attribute);
     }
 
-    internal static List<Cell> ToCells (IEnumerable<Rune> runes, Attribute? attribute = null)
+    internal static List<Cell> ToCells (IEnumerable<string> strings, Attribute? attribute = null)
     {
-        List<Cell> cells = new ();
+        StringBuilder sb = new ();
 
-        foreach (Rune rune in runes)
+        foreach (string str in strings)
         {
-            cells.Add (new () { Rune = rune, Attribute = attribute });
+            sb.Append (str);
         }
 
-        return cells;
+        return ToCellList (sb.ToString (), attribute);
     }
 
     private static List<List<Cell>> SplitNewLines (List<Cell> cells)
@@ -179,14 +194,15 @@ public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Ru
         // ASCII code 10 = Line Feed.
         for (; i < cells.Count; i++)
         {
-            if (cells [i].Rune.Value == 13)
+            if (cells [i].Grapheme.Length == 1 && cells [i].Grapheme [0] == 13)
             {
                 hasCR = true;
 
                 continue;
             }
 
-            if (cells [i].Rune.Value == 10)
+            if ((cells [i].Grapheme.Length == 1 && cells [i].Grapheme [0] == 10)
+                || cells [i].Grapheme == "\r\n")
             {
                 if (i - start > 0)
                 {

+ 49 - 0
Terminal.Gui/Drawing/GraphemeHelper.cs

@@ -0,0 +1,49 @@
+using System.Globalization;
+
+namespace Terminal.Gui.Drawing;
+
+/// <summary>
+///     Provides utility methods for enumerating Unicode grapheme clusters (user-perceived characters)
+///     in a string. A grapheme cluster may consist of one or more <see cref="Rune"/> values,
+///     including combining marks or zero-width joiner (ZWJ) sequences such as emoji family groups.
+/// </summary>
+/// <remarks>
+///     <para>
+///         This helper uses <see cref="StringInfo.GetTextElementEnumerator(string)"/> to enumerate
+///         text elements according to the Unicode Standard Annex #29 (UAX #29) rules for
+///         extended grapheme clusters.
+///     </para>
+///     <para>
+///         On legacy Windows consoles (e.g., <c>cmd.exe</c>, <c>conhost.exe</c>), complex grapheme
+///         sequences such as ZWJ emoji or combining marks may not render correctly, even though
+///         the underlying string data is valid.
+///     </para>
+///     <para>
+///         For most accurate visual rendering, prefer modern terminals such as Windows Terminal
+///         or Linux-based terminals with full Unicode and font support.
+///     </para>
+/// </remarks>
+public static class GraphemeHelper
+{
+    /// <summary>
+    ///     Enumerates extended grapheme clusters from a string.
+    ///     Handles surrogate pairs, combining marks, and basic ZWJ sequences.
+    ///     Safe for legacy consoles; memory representation is correct.
+    /// </summary>
+    public static IEnumerable<string> GetGraphemes (string text)
+    {
+        if (string.IsNullOrEmpty (text))
+        {
+            yield break;
+        }
+
+        TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator (text);
+
+        while (enumerator.MoveNext ())
+        {
+            string element = enumerator.GetTextElement ();
+
+            yield return element;
+        }
+    }
+}

+ 4 - 5
Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs

@@ -173,7 +173,7 @@ public class LineCanvas : IDisposable
                 intersectionsBufferList.Clear ();
                 foreach (var line in _lines)
                 {
-                    if (line.Intersects (x, y) is IntersectionDefinition intersect)
+                    if (line.Intersects (x, y) is { } intersect)
                     {
                         intersectionsBufferList.Add (intersect);
                     }
@@ -217,9 +217,8 @@ public class LineCanvas : IDisposable
             for (int x = inArea.X; x < inArea.X + inArea.Width; x++)
             {
                 IntersectionDefinition [] intersects = _lines
-                    // ! nulls are filtered out by the next Where filter
-                    .Select (l => l.Intersects (x, y)!)
-                    .Where (i => i is not null)
+                    .Select (l => l.Intersects (x, y))
+                    .OfType<IntersectionDefinition> () // automatically filters nulls and casts
                     .ToArray ();
 
                 Rune? rune = GetRuneForIntersects (intersects);
@@ -413,7 +412,7 @@ public class LineCanvas : IDisposable
 
         if (rune.HasValue)
         {
-            cell.Rune = rune.Value;
+            cell.Grapheme = rune.ToString ()!;
         }
 
         cell.Attribute = GetAttributeForIntersects (intersects);

+ 13 - 17
Terminal.Gui/Drivers/DriverImpl.cs

@@ -253,8 +253,16 @@ internal class DriverImpl : IDriver
     /// <inheritdoc/>
     public bool IsRuneSupported (Rune rune) => Rune.IsValid (rune.Value);
 
-    /// <inheritdoc/>
-    public bool IsValidLocation (Rune rune, int col, int row) => OutputBuffer.IsValidLocation (rune, col, row);
+    /// <summary>Tests whether the specified coordinate are valid for drawing the specified Text.</summary>
+    /// <param name="text">Used to determine if one or two columns are required.</param>
+    /// <param name="col">The column.</param>
+    /// <param name="row">The row.</param>
+    /// <returns>
+    ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of
+    ///     <see cref="IDriver.Clip"/>.
+    ///     <see langword="true"/> otherwise.
+    /// </returns>
+    public bool IsValidLocation (string text, int col, int row) { return OutputBuffer.IsValidLocation (text, col, row); }
 
     /// <inheritdoc/>
     public void Move (int col, int row) { OutputBuffer.Move (col, row); }
@@ -379,26 +387,14 @@ internal class DriverImpl : IDriver
         {
             for (var c = 0; c < Cols; c++)
             {
-                Rune rune = contents [r, c].Rune;
+                string text = contents [r, c].Grapheme;
 
-                if (rune.DecodeSurrogatePair (out char []? sp))
-                {
-                    sb.Append (sp);
-                }
-                else
-                {
-                    sb.Append ((char)rune.Value);
-                }
+                sb.Append (text);
 
-                if (rune.GetColumns () > 1)
+                if (text.GetColumns () > 1)
                 {
                     c++;
                 }
-
-                // See Issue #2616
-                //foreach (var combMark in contents [r, c].CombiningMarks) {
-                //	sb.Append ((char)combMark.Value);
-                //}
             }
 
             sb.AppendLine ();

+ 3 - 3
Terminal.Gui/Drivers/IDriver.cs

@@ -124,8 +124,8 @@ public interface IDriver
     /// </returns>
     bool IsRuneSupported (Rune rune);
 
-    /// <summary>Tests whether the specified coordinate are valid for drawing the specified Rune.</summary>
-    /// <param name="rune">Used to determine if one or two columns are required.</param>
+    /// <summary>Tests whether the specified coordinate are valid for drawing the specified Text.</summary>
+    /// <param name="text">Used to determine if one or two columns are required.</param>
     /// <param name="col">The column.</param>
     /// <param name="row">The row.</param>
     /// <returns>
@@ -133,7 +133,7 @@ public interface IDriver
     ///     <see cref="IDriver.Clip"/>.
     ///     <see langword="true"/> otherwise.
     /// </returns>
-    bool IsValidLocation (Rune rune, int col, int row);
+    bool IsValidLocation (string text, int col, int row);
 
     /// <summary>
     ///     Updates <see cref="IDriver.Col"/> and <see cref="IDriver.Row"/> to the specified column and row in

+ 4 - 4
Terminal.Gui/Drivers/IOutputBuffer.cs

@@ -84,15 +84,15 @@ public interface IOutputBuffer
     void FillRect (Rectangle rect, char rune);
 
     /// <summary>
-    ///     Tests whether the specified coordinate is valid for drawing the specified Rune.
+    ///     Tests whether the specified coordinate is valid for drawing the specified Text.
     /// </summary>
-    /// <param name="rune">Used to determine if one or two columns are required.</param>
+    /// <param name="text">Used to determine if one or two columns are required.</param>
     /// <param name="col">The column.</param>
     /// <param name="row">The row.</param>
     /// <returns>
-    ///     True if the coordinate is valid for the Rune; false otherwise.
+    ///     True if the coordinate is valid for the Text; false otherwise.
     /// </returns>
-    bool IsValidLocation (Rune rune, int col, int row);
+    bool IsValidLocation (string text, int col, int row);
 
     /// <summary>
     ///     The first cell index on left of screen - basically always 0.

+ 5 - 28
Terminal.Gui/Drivers/OutputBase.cs

@@ -76,25 +76,6 @@ public abstract class OutputBase
 
                     outputWidth++;
 
-                    // Handle special cases that AppendCellAnsi doesn't cover
-                    Rune rune = cell.Rune;
-                    if (cell.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);
-                    }
-                    else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
-                    {
-                        WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                        SetCursorPositionImpl (col - 1, row);
-                    }
-
                     buffer.Contents [row, col].IsDirty = false;
                 }
             }
@@ -216,16 +197,12 @@ public abstract class OutputBase
             redrawTextStyle = attribute.Value.Style;
         }
 
-        // Add the character
-        const int MAX_CHARS_PER_RUNE = 2;
-        Span<char> runeBuffer = stackalloc char [MAX_CHARS_PER_RUNE];
-        Rune rune = cell.Rune;
-        int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer);
-        ReadOnlySpan<char> runeChars = runeBuffer [..runeCharsWritten];
-        output.Append (runeChars);
+        // Add the grapheme
+        string grapheme = cell.Grapheme;
+        output.Append (grapheme);
 
-        // Handle wide characters
-        if (rune.GetColumns () > 1 && currentCol + 1 < maxCol)
+        // Handle wide grapheme
+        if (grapheme.GetColumns () > 1 && currentCol + 1 < maxCol)
         {
             currentCol++; // Skip next cell for wide character
         }

+ 72 - 142
Terminal.Gui/Drivers/OutputBufferImpl.cs

@@ -65,7 +65,9 @@ public class OutputBufferImpl : IOutputBuffer
     /// <summary>The topmost row in the terminal.</summary>
     public virtual int Top { get; set; } = 0;
 
-    /// <inheritdoc/>
+    /// <summary>
+    /// Indicates which lines have been modified and need to be redrawn.
+    /// </summary>
     public bool [] DirtyLines { get; set; } = [];
 
     // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application?
@@ -112,85 +114,50 @@ public class OutputBufferImpl : IOutputBuffer
     ///         will be added instead.
     ///     </para>
     /// </remarks>
-    /// <param name="rune">Rune to add.</param>
-    public void AddRune (Rune rune)
-    {
-        int runeWidth = -1;
-        bool validLocation = IsValidLocation (rune, Col, Row);
-
-        if (Contents is null)
-        {
-            return;
-        }
-
-        Clip ??= new (Screen);
+    /// <param name="rune">Text to add.</param>
+    public void AddRune (Rune rune) { AddStr (rune.ToString ()); }
 
-        Rectangle clipRect = Clip!.GetBounds ();
+    /// <summary>
+    ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
+    ///     convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
+    /// </summary>
+    /// <param name="c">Character to add.</param>
+    public void AddRune (char c) { AddRune (new Rune (c)); }
 
-        if (validLocation)
+    /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         When the method returns, <see cref="Col"/> will be incremented by the number of columns
+    ///         <paramref name="str"/> required, unless the new column value is outside the <see cref="Clip"/> or screen
+    ///         dimensions defined by <see cref="Cols"/>.
+    ///     </para>
+    ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
+    /// </remarks>
+    /// <param name="str">String.</param>
+    public void AddStr (string str)
+    {
+        foreach (string grapheme in GraphemeHelper.GetGraphemes (str))
         {
-            rune = rune.MakePrintable ();
-            runeWidth = rune.GetColumns ();
+            string text = grapheme;
 
-            lock (Contents)
-            {
-                if (runeWidth == 0 && rune.IsCombiningMark ())
-                {
-                    // 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 `[é  ]`.
-                    // 
-                    // Until this is addressed (see Issue #), we do our best by 
-                    // a) Attempting to normalize any CM with the base char to it's left
-                    // b) Ignoring any CMs that don't normalize
-                    if (Col > 0)
-                    {
-                        if (Contents [Row, Col - 1].CombiningMarks.Count > 0)
-                        {
-                            // Just add this mark to the list
-                            Contents [Row, Col - 1].AddCombiningMark (rune);
-
-                            // Ignore. Don't move to next column (let the driver figure out what to do).
-                        }
-                        else
-                        {
-                            // Attempt to normalize the cell to our left combined with this mark
-                            string combined = Contents [Row, Col - 1].Rune + rune.ToString ();
+            int textWidth = -1;
+            bool validLocation = IsValidLocation (text, Col, Row);
 
-                            // Normalize to Form C (Canonical Composition)
-                            string normalized = combined.Normalize (NormalizationForm.FormC);
+            if (Contents is null)
+            {
+                return;
+            }
 
-                            if (normalized.Length == 1)
-                            {
-                                // It normalized! We can just set the Cell to the left with the
-                                // normalized codepoint 
-                                Contents [Row, Col - 1].Rune = (Rune)normalized [0];
+            Clip ??= new (Screen);
 
-                                // Ignore. Don't move to next column because we're already there
-                            }
-                            else
-                            {
-                                // It didn't normalize. Add it to the Cell to left's CM list
-                                Contents [Row, Col - 1].AddCombiningMark (rune);
+            Rectangle clipRect = Clip!.GetBounds ();
 
-                                // Ignore. Don't move to next column (let the driver figure out what to do).
-                            }
-                        }
+            if (validLocation)
+            {
+                text = text.MakePrintable ();
+                textWidth = text.GetColumns ();
 
-                        Contents [Row, Col - 1].Attribute = CurrentAttribute;
-                        Contents [Row, Col - 1].IsDirty = true;
-                    }
-                    else
-                    {
-                        // Most drivers will render a combining mark at col 0 as the mark
-                        Contents [Row, Col].Rune = rune;
-                        Contents [Row, Col].Attribute = CurrentAttribute;
-                        Contents [Row, Col].IsDirty = true;
-                        Col++;
-                    }
-                }
-                else
+                lock (Contents)
                 {
                     Contents [Row, Col].Attribute = CurrentAttribute;
                     Contents [Row, Col].IsDirty = true;
@@ -198,49 +165,45 @@ public class OutputBufferImpl : IOutputBuffer
                     if (Col > 0)
                     {
                         // Check if cell to left has a wide glyph
-                        if (Contents [Row, Col - 1].Rune.GetColumns () > 1)
+                        if (Contents [Row, Col - 1].Grapheme.GetColumns () > 1)
                         {
                             // Invalidate cell to left
-                            Contents [Row, Col - 1].Rune = Rune.ReplacementChar;
+                            Contents [Row, Col - 1].Grapheme = Rune.ReplacementChar.ToString ();
                             Contents [Row, Col - 1].IsDirty = true;
                         }
                     }
 
-                    if (runeWidth < 1)
-                    {
-                        Contents [Row, Col].Rune = Rune.ReplacementChar;
-                    }
-                    else if (runeWidth == 1)
+                    if (textWidth is 0 or 1)
                     {
-                        Contents [Row, Col].Rune = rune;
+                        Contents [Row, Col].Grapheme = text;
 
                         if (Col < clipRect.Right - 1)
                         {
                             Contents [Row, Col + 1].IsDirty = true;
                         }
                     }
-                    else if (runeWidth == 2)
+                    else if (textWidth == 2)
                     {
                         if (!Clip.Contains (Col + 1, Row))
                         {
                             // We're at the right edge of the clip, so we can't display a wide character.
                             // TODO: Figure out if it is better to show a replacement character or ' '
-                            Contents [Row, Col].Rune = Rune.ReplacementChar;
+                            Contents [Row, Col].Grapheme = Rune.ReplacementChar.ToString ();
                         }
                         else if (!Clip.Contains (Col, Row))
                         {
                             // Our 1st column is outside the clip, so we can't display a wide character.
-                            Contents [Row, Col + 1].Rune = Rune.ReplacementChar;
+                            Contents [Row, Col + 1].Grapheme = Rune.ReplacementChar.ToString ();
                         }
                         else
                         {
-                            Contents [Row, Col].Rune = rune;
+                            Contents [Row, Col].Grapheme = text;
 
                             if (Col < clipRect.Right - 1)
                             {
                                 // Invalidate cell to right so that it doesn't get drawn
                                 // TODO: Figure out if it is better to show a replacement character or ' '
-                                Contents [Row, Col + 1].Rune = Rune.ReplacementChar;
+                                Contents [Row, Col + 1].Grapheme = Rune.ReplacementChar.ToString ();
                                 Contents [Row, Col + 1].IsDirty = true;
                             }
                         }
@@ -248,67 +211,37 @@ public class OutputBufferImpl : IOutputBuffer
                     else
                     {
                         // This is a non-spacing character, so we don't need to do anything
-                        Contents [Row, Col].Rune = (Rune)' ';
+                        Contents [Row, Col].Grapheme = " ";
                         Contents [Row, Col].IsDirty = false;
                     }
 
                     DirtyLines [Row] = true;
                 }
             }
-        }
 
-        if (runeWidth is < 0 or > 0)
-        {
             Col++;
-        }
 
-        if (runeWidth > 1)
-        {
-            Debug.Assert (runeWidth <= 2);
-
-            if (validLocation && Col < clipRect.Right)
+            if (textWidth > 1)
             {
-                lock (Contents!)
+                Debug.Assert (textWidth <= 2);
+
+                if (validLocation && Col < clipRect.Right)
                 {
-                    // This is a double-width character, and we are not at the end of the line.
-                    // Col now points to the second column of the character. Ensure it doesn't
-                    // Get rendered.
-                    Contents [Row, Col].IsDirty = false;
-                    Contents [Row, Col].Attribute = CurrentAttribute;
+                    lock (Contents!)
+                    {
+                        // This is a double-width character, and we are not at the end of the line.
+                        // Col now points to the second column of the character. Ensure it doesn't
+                        // Get rendered.
+                        Contents [Row, Col].IsDirty = false;
+                        Contents [Row, Col].Attribute = CurrentAttribute;
 
-                    // TODO: Determine if we should wipe this out (for now now)
-                    //Contents [Row, Col].Rune = (Rune)' ';
+                        // TODO: Determine if we should wipe this out (for now now)
+                        //Contents [Row, Col].Text = (Text)' ';
+                    }
                 }
-            }
 
-            Col++;
-        }
-    }
-
-    /// <summary>
-    ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
-    ///     convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
-    /// </summary>
-    /// <param name="c">Character to add.</param>
-    public void AddRune (char c) { AddRune (new Rune (c)); }
-
-    /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         When the method returns, <see cref="Col"/> will be incremented by the number of columns
-    ///         <paramref name="str"/> required, unless the new column value is outside of the <see cref="Clip"/> or screen
-    ///         dimensions defined by <see cref="Cols"/>.
-    ///     </para>
-    ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
-    /// </remarks>
-    /// <param name="str">String.</param>
-    public void AddStr (string str)
-    {
-        List<Rune> runes = str.EnumerateRunes ().ToList ();
-
-        for (var i = 0; i < runes.Count; i++)
-        {
-            AddRune (runes [i]);
+                Col++;
+            }
         }
     }
 
@@ -331,7 +264,7 @@ public class OutputBufferImpl : IOutputBuffer
                 {
                     Contents [row, c] = new ()
                     {
-                        Rune = (Rune)' ',
+                        Grapheme = " ",
                         Attribute = new Attribute (Color.White, Color.Black),
                         IsDirty = true
                     };
@@ -345,22 +278,19 @@ public class OutputBufferImpl : IOutputBuffer
         //ClearedContents?.Invoke (this, EventArgs.Empty);
     }
 
-    /// <summary>Tests whether the specified coordinate are valid for drawing the specified Rune.</summary>
-    /// <param name="rune">Used to determine if one or two columns are required.</param>
+    /// <summary>Tests whether the specified coordinate are valid for drawing the specified Text.</summary>
+    /// <param name="text">Used to determine if one or two columns are required.</param>
     /// <param name="col">The column.</param>
     /// <param name="row">The row.</param>
     /// <returns>
     ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of <see cref="Clip"/>.
     ///     <see langword="true"/> otherwise.
     /// </returns>
-    public bool IsValidLocation (Rune rune, int col, int row)
+    public bool IsValidLocation (string text, int col, int row)
     {
-        if (rune.GetColumns () < 2)
-        {
-            return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip!.Contains (col, row);
-        }
+        int textWidth = text.GetColumns ();
 
-        return Clip!.Contains (col, row) || Clip!.Contains (col + 1, row);
+        return col >= 0 && row >= 0 && col + textWidth <= Cols && row < Rows && Clip!.Contains (col, row);
     }
 
     /// <inheritdoc/>
@@ -383,14 +313,14 @@ public class OutputBufferImpl : IOutputBuffer
             {
                 for (int c = rect.X; c < rect.X + rect.Width; c++)
                 {
-                    if (!IsValidLocation (rune, c, r))
+                    if (!IsValidLocation (rune.ToString (), c, r))
                     {
                         continue;
                     }
 
                     Contents [r, c] = new ()
                     {
-                        Rune = rune != default (Rune) ? rune : (Rune)' ',
+                        Grapheme = rune != default (Rune) ? rune.ToString () : " ",
                         Attribute = CurrentAttribute, IsDirty = true
                     };
                 }

+ 76 - 12
Terminal.Gui/Text/StringExtensions.cs

@@ -1,5 +1,4 @@
 using System.Buffers;
-using System.Globalization;
 
 namespace Terminal.Gui.Text;
 
@@ -54,8 +53,9 @@ public static class StringExtensions
     /// <summary>Gets the number of columns the string occupies in the terminal.</summary>
     /// <remarks>This is a Terminal.Gui extension method to <see cref="string"/> to support TUI text manipulation.</remarks>
     /// <param name="str">The string to measure.</param>
+    /// <param name="ignoreLessThanZero">Indicates whether to ignore values ​​less than zero, such as control keys.</param>
     /// <returns></returns>
-    public static int GetColumns (this string str)
+    public static int GetColumns (this string str, bool ignoreLessThanZero = true)
     {
         if (string.IsNullOrEmpty (str))
         {
@@ -63,17 +63,25 @@ public static class StringExtensions
         }
 
         var total = 0;
-        TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator (str);
 
-        while (enumerator.MoveNext ())
+        foreach (string grapheme in GraphemeHelper.GetGraphemes (str))
         {
-            string element = enumerator.GetTextElement ();
-
             // Get the maximum rune width within this grapheme cluster
-            int width = element
-                        .EnumerateRunes ()
-                        .Max (r => Math.Max (r.GetColumns (), 0));
-            total += width;
+            int clusterWidth = grapheme.EnumerateRunes ()
+                                       .Sum (r =>
+                                             {
+                                                 int w = r.GetColumns ();
+
+                                                 return ignoreLessThanZero && w < 0 ? 0 : w;
+                                             });
+
+            // Clamp to realistic max display width
+            if (clusterWidth > 2)
+            {
+                clusterWidth = 2;
+            }
+
+            total += clusterWidth;
         }
 
         return total;
@@ -94,7 +102,7 @@ public static class StringExtensions
     ///     A <see langword="bool"/> indicating if all elements of the <see cref="ReadOnlySpan{T}"/> are ASCII digits (
     ///     <see langword="true"/>) or not (<see langword="false"/>
     /// </returns>
-    public static bool IsAllAsciiDigits (this ReadOnlySpan<char> stringSpan) { return stringSpan.ToString ().All (char.IsAsciiDigit); }
+    public static bool IsAllAsciiDigits (this ReadOnlySpan<char> stringSpan) { return !stringSpan.IsEmpty && stringSpan.ToString ().All (char.IsAsciiDigit); }
 
     /// <summary>
     ///     Determines if this <see cref="ReadOnlySpan{T}"/> of <see langword="char"/> is composed entirely of ASCII
@@ -105,7 +113,7 @@ public static class StringExtensions
     ///     A <see langword="bool"/> indicating if all elements of the <see cref="ReadOnlySpan{T}"/> are ASCII digits (
     ///     <see langword="true"/>) or not (<see langword="false"/>
     /// </returns>
-    public static bool IsAllAsciiHexDigits (this ReadOnlySpan<char> stringSpan) { return stringSpan.ToString ().All (char.IsAsciiHexDigit); }
+    public static bool IsAllAsciiHexDigits (this ReadOnlySpan<char> stringSpan) { return !stringSpan.IsEmpty && stringSpan.ToString ().All (char.IsAsciiHexDigit); }
 
     /// <summary>Repeats the string <paramref name="n"/> times.</summary>
     /// <remarks>This is a Terminal.Gui extension method to <see cref="string"/> to support TUI text manipulation.</remarks>
@@ -206,4 +214,60 @@ public static class StringExtensions
 
         return encoding.GetString (bytes.ToArray ());
     }
+
+    /// <summary>Converts a <see cref="string"/> generic collection into a string.</summary>
+    /// <param name="strings">The enumerable string to convert.</param>
+    /// <returns></returns>
+    public static string ToString (IEnumerable<string> strings) { return string.Concat (strings); }
+
+    /// <summary>Converts the string into a <see cref="List{String}"/>.</summary>
+    /// <remarks>This is a Terminal.Gui extension method to <see cref="string"/> to support TUI text manipulation.</remarks>
+    /// <param name="str">The string to convert.</param>
+    /// <returns></returns>
+    public static List<string> ToStringList (this string str)
+    {
+        List<string> strings = [];
+
+        foreach (string grapheme in GraphemeHelper.GetGraphemes (str))
+        {
+            strings.Add (grapheme);
+        }
+
+        return strings;
+    }
+
+    /// <summary>Reports whether a string is a surrogate code point.</summary>
+    /// <remarks>This is a Terminal.Gui extension method to <see cref="string"/> to support TUI text manipulation.</remarks>
+    /// <param name="str">The string to probe.</param>
+    /// <returns><see langword="true"/> if the string is a surrogate code point; <see langword="false"/> otherwise.</returns>
+    public static bool IsSurrogatePair (this string str)
+    {
+        if (str.Length != 2)
+        {
+            return false;
+        }
+
+        Rune rune = Rune.GetRuneAt (str, 0);
+
+        return rune.IsSurrogatePair ();
+    }
+
+    /// <summary>
+    ///     Ensures the text is not a control character and can be displayed by translating characters below 0x20 to
+    ///     equivalent, printable, Unicode chars.
+    /// </summary>
+    /// <remarks>This is a Terminal.Gui extension method to <see cref="string"/> to support TUI text manipulation.</remarks>
+    /// <param name="str">The text.</param>
+    /// <returns></returns>
+    public static string MakePrintable (this string str)
+    {
+        if (str.Length > 1)
+        {
+            return str;
+        }
+
+        char ch = str [0];
+
+        return char.IsControl (ch) ? new ((char)(ch + 0x2400), 1) : str;
+    }
 }

+ 169 - 159
Terminal.Gui/Text/TextFormatter.cs

@@ -122,9 +122,10 @@ public class TextFormatter
                 break;
             }
 
-            Rune [] runes = linesFormatted [line].ToRunes ();
+            string strings = linesFormatted [line];
+            string[] graphemes = GraphemeHelper.GetGraphemes (strings).ToArray ();
 
-            // When text is justified, we lost left or right, so we use the direction to align. 
+            // When text is justified, we lost left or right, so we use the direction to align.
 
             int x = 0, y = 0;
 
@@ -139,7 +140,7 @@ public class TextFormatter
                 }
                 else
                 {
-                    int runesWidth = StringExtensions.ToString (runes).GetColumns ();
+                    int runesWidth = strings.GetColumns ();
                     x = screen.Right - runesWidth;
                     CursorPosition = screen.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
                 }
@@ -195,7 +196,7 @@ public class TextFormatter
                 }
                 else
                 {
-                    int runesWidth = StringExtensions.ToString (runes).GetColumns ();
+                    int runesWidth = strings.GetColumns ();
                     x = screen.Left + (screen.Width - runesWidth) / 2;
 
                     CursorPosition = (screen.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
@@ -213,7 +214,7 @@ public class TextFormatter
             {
                 if (isVertical)
                 {
-                    y = screen.Bottom - runes.Length;
+                    y = screen.Bottom - graphemes.Length;
                 }
                 else
                 {
@@ -249,7 +250,7 @@ public class TextFormatter
             {
                 if (isVertical)
                 {
-                    int s = (screen.Height - runes.Length) / 2;
+                    int s = (screen.Height - graphemes.Length) / 2;
                     y = screen.Top + s;
                 }
                 else
@@ -270,14 +271,14 @@ public class TextFormatter
             int size = isVertical ? screen.Height : screen.Width;
             int current = start + colOffset;
             List<Point?> lastZeroWidthPos = null!;
-            Rune rune = default;
-            int zeroLengthCount = isVertical ? runes.Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0;
+            string text = default;
+            int zeroLengthCount = isVertical ? strings.EnumerateRunes ().Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0;
 
             for (int idx = (isVertical ? start - y : start - x) + colOffset;
                  current < start + size + zeroLengthCount;
                  idx++)
             {
-                Rune lastRuneUsed = rune;
+                string lastTextUsed = text;
 
                 if (lastZeroWidthPos is null)
                 {
@@ -291,17 +292,17 @@ public class TextFormatter
                         continue;
                     }
 
-                    if (!FillRemaining && idx > runes.Length - 1)
+                    if (!FillRemaining && idx > graphemes.Length - 1)
                     {
                         break;
                     }
 
                     if ((!isVertical
                          && (current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset
-                             || (idx < runes.Length && runes [idx].GetColumns () > screen.Width)))
+                             || (idx < graphemes.Length && graphemes [idx].GetColumns () > screen.Width)))
                         || (isVertical
                             && ((current > start + size + zeroLengthCount && idx > maxScreen.Top + maxScreen.Height - screen.Y)
-                                || (idx < runes.Length && runes [idx].GetColumns () > screen.Width))))
+                                || (idx < graphemes.Length && graphemes [idx].GetColumns () > screen.Width))))
                     {
                         break;
                     }
@@ -312,13 +313,13 @@ public class TextFormatter
 
                 //	break;
 
-                rune = (Rune)' ';
+                text = " ";
 
                 if (isVertical)
                 {
-                    if (idx >= 0 && idx < runes.Length)
+                    if (idx >= 0 && idx < graphemes.Length)
                     {
-                        rune = runes [idx];
+                        text = graphemes [idx];
                     }
 
                     if (lastZeroWidthPos is null)
@@ -334,7 +335,7 @@ public class TextFormatter
 
                         if (foundIdx > -1)
                         {
-                            if (rune.IsCombiningMark ())
+                            if (Rune.GetRuneAt (text, 0).IsCombiningMark ())
                             {
                                 lastZeroWidthPos [foundIdx] =
                                     new Point (
@@ -347,7 +348,7 @@ public class TextFormatter
                                               current
                                              );
                             }
-                            else if (!rune.IsCombiningMark () && lastRuneUsed.IsCombiningMark ())
+                            else if (!Rune.GetRuneAt (text, 0).IsCombiningMark () && Rune.GetRuneAt (lastTextUsed, 0).IsCombiningMark ())
                             {
                                 current++;
                                 driver?.Move (x, current);
@@ -367,13 +368,13 @@ public class TextFormatter
                 {
                     driver?.Move (current, y);
 
-                    if (idx >= 0 && idx < runes.Length)
+                    if (idx >= 0 && idx < graphemes.Length)
                     {
-                        rune = runes [idx];
+                        text = graphemes [idx];
                     }
                 }
 
-                int runeWidth = GetRuneWidth (rune, TabWidth);
+                int runeWidth = GetTextWidth (text, TabWidth);
 
                 if (HotKeyPos > -1 && idx == HotKeyPos)
                 {
@@ -383,7 +384,7 @@ public class TextFormatter
                     }
 
                     driver?.SetAttribute (hotColor);
-                    driver?.AddRune (rune);
+                    driver?.AddStr (text);
                     driver?.SetAttribute (normalColor);
                 }
                 else
@@ -412,7 +413,7 @@ public class TextFormatter
                         }
                     }
 
-                    driver?.AddRune (rune);
+                    driver?.AddStr (text);
                 }
 
                 if (isVertical)
@@ -427,11 +428,11 @@ public class TextFormatter
                     current += runeWidth;
                 }
 
-                int nextRuneWidth = idx + 1 > -1 && idx + 1 < runes.Length
-                                        ? runes [idx + 1].GetColumns ()
+                int nextRuneWidth = idx + 1 > -1 && idx + 1 < graphemes.Length
+                                        ? graphemes [idx + 1].GetColumns ()
                                         : 0;
 
-                if (!isVertical && idx + 1 < runes.Length && current + nextRuneWidth > start + size)
+                if (!isVertical && idx + 1 < graphemes.Length && current + nextRuneWidth > start + size)
                 {
                     break;
                 }
@@ -929,9 +930,10 @@ public class TextFormatter
                 break;
             }
 
-            Rune [] runes = linesFormatted [line].ToRunes ();
+            string strings = linesFormatted [line];
+            string [] graphemes = GraphemeHelper.GetGraphemes (strings).ToArray ();
 
-            // When text is justified, we lost left or right, so we use the direction to align. 
+            // When text is justified, we lost left or right, so we use the direction to align.
             int x = 0, y = 0;
 
             switch (Alignment)
@@ -946,17 +948,17 @@ public class TextFormatter
                     }
                 case Alignment.End:
                     {
-                        int runesWidth = StringExtensions.ToString (runes).GetColumns ();
-                        x = screen.Right - runesWidth;
+                        int stringsWidth = strings.GetColumns ();
+                        x = screen.Right - stringsWidth;
 
                         break;
                     }
                 case Alignment.Start when isVertical:
                     {
-                        int runesWidth = line > 0
-                                         ? GetColumnsRequiredForVerticalText (linesFormatted, 0, line, TabWidth)
-                                         : 0;
-                        x = screen.Left + runesWidth;
+                        int stringsWidth = line > 0
+                                               ? GetColumnsRequiredForVerticalText (linesFormatted, 0, line, TabWidth)
+                                               : 0;
+                        x = screen.Left + stringsWidth;
 
                         break;
                     }
@@ -966,7 +968,7 @@ public class TextFormatter
                     break;
                 case Alignment.Fill when isVertical:
                     {
-                        int runesWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, linesFormatted.Count, TabWidth);
+                        int stringsWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, linesFormatted.Count, TabWidth);
                         int prevLineWidth = line > 0 ? GetColumnsRequiredForVerticalText (linesFormatted, line - 1, 1, TabWidth) : 0;
                         int firstLineWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, 1, TabWidth);
                         int lastLineWidth = GetColumnsRequiredForVerticalText (linesFormatted, linesFormatted.Count - 1, 1, TabWidth);
@@ -975,7 +977,7 @@ public class TextFormatter
                         x = line == 0
                                 ? screen.Left
                                 : line < linesFormatted.Count - 1
-                                    ? screen.Width - runesWidth <= lastLineWidth ? screen.Left + prevLineWidth : screen.Left + line * interval
+                                    ? screen.Width - stringsWidth <= lastLineWidth ? screen.Left + prevLineWidth : screen.Left + line * interval
                                     : screen.Right - lastLineWidth;
 
                         break;
@@ -986,16 +988,16 @@ public class TextFormatter
                     break;
                 case Alignment.Center when isVertical:
                     {
-                        int runesWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, linesFormatted.Count, TabWidth);
+                        int stringsWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, linesFormatted.Count, TabWidth);
                         int linesWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, line, TabWidth);
-                        x = screen.Left + linesWidth + (screen.Width - runesWidth) / 2;
+                        x = screen.Left + linesWidth + (screen.Width - stringsWidth) / 2;
 
                         break;
                     }
                 case Alignment.Center:
                     {
-                        int runesWidth = StringExtensions.ToString (runes).GetColumns ();
-                        x = screen.Left + (screen.Width - runesWidth) / 2;
+                        int stringsWidth = strings.GetColumns ();
+                        x = screen.Left + (screen.Width - stringsWidth) / 2;
 
                         break;
                     }
@@ -1009,7 +1011,7 @@ public class TextFormatter
             {
                 // Vertical Alignment
                 case Alignment.End when isVertical:
-                    y = screen.Bottom - runes.Length;
+                    y = screen.Bottom - graphemes.Length;
 
                     break;
                 case Alignment.End:
@@ -1039,7 +1041,7 @@ public class TextFormatter
                     }
                 case Alignment.Center when isVertical:
                     {
-                        int s = (screen.Height - runes.Length) / 2;
+                        int s = (screen.Height - graphemes.Length) / 2;
                         y = screen.Top + s;
 
                         break;
@@ -1061,7 +1063,7 @@ public class TextFormatter
             int start = isVertical ? screen.Top : screen.Left;
             int size = isVertical ? screen.Height : screen.Width;
             int current = start + colOffset;
-            int zeroLengthCount = isVertical ? runes.Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0;
+            int zeroLengthCount = isVertical ? strings.EnumerateRunes ().Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0;
 
             int lineX = x, lineY = y, lineWidth = 0, lineHeight = 1;
 
@@ -1079,23 +1081,23 @@ public class TextFormatter
                     continue;
                 }
 
-                if (!FillRemaining && idx > runes.Length - 1)
+                if (!FillRemaining && idx > graphemes.Length - 1)
                 {
                     break;
                 }
 
                 if ((!isVertical
                      && (current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset
-                         || (idx < runes.Length && runes [idx].GetColumns () > screen.Width)))
+                         || (idx < graphemes.Length && graphemes [idx].GetColumns () > screen.Width)))
                     || (isVertical
                         && ((current > start + size + zeroLengthCount && idx > maxScreen.Top + maxScreen.Height - screen.Y)
-                            || (idx < runes.Length && runes [idx].GetColumns () > screen.Width))))
+                            || (idx < graphemes.Length && graphemes [idx].GetColumns () > screen.Width))))
                 {
                     break;
                 }
 
-                Rune rune = idx >= 0 && idx < runes.Length ? runes [idx] : (Rune)' ';
-                int runeWidth = GetRuneWidth (rune, TabWidth);
+                string text = idx >= 0 && idx < graphemes.Length ? graphemes [idx] : " ";
+                int runeWidth = GetStringWidth (text, TabWidth);
 
                 if (isVertical)
                 {
@@ -1114,11 +1116,11 @@ public class TextFormatter
 
                 current += isVertical && runeWidth > 0 ? 1 : runeWidth;
 
-                int nextRuneWidth = idx + 1 > -1 && idx + 1 < runes.Length
-                                        ? runes [idx + 1].GetColumns ()
+                int nextStringWidth = idx + 1 > -1 && idx + 1 < graphemes.Length
+                                        ? graphemes [idx + 1].GetColumns ()
                                         : 0;
 
-                if (!isVertical && idx + 1 < runes.Length && current + nextRuneWidth > start + size)
+                if (!isVertical && idx + 1 < graphemes.Length && current + nextStringWidth > start + size)
                 {
                     break;
                 }
@@ -1331,33 +1333,34 @@ public class TextFormatter
     /// <returns>A list of text without the newline characters.</returns>
     public static List<string> SplitNewLine (string text)
     {
-        List<Rune> runes = text.ToRuneList ();
+        List<string> graphemes = GraphemeHelper.GetGraphemes (text).ToList ();
         List<string> lines = new ();
         var start = 0;
 
-        for (var i = 0; i < runes.Count; i++)
+        for (var i = 0; i < graphemes.Count; i++)
         {
             int end = i;
 
-            switch (runes [i].Value)
+            switch (graphemes [i])
             {
-                case '\n':
-                    lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
+                case "\n":
+                case "\r\n":
+                    lines.Add (StringExtensions.ToString (graphemes.GetRange (start, end - start)));
                     i++;
                     start = i;
 
                     break;
 
-                case '\r':
-                    if (i + 1 < runes.Count && runes [i + 1].Value == '\n')
+                case "\r":
+                    if (i + 1 < graphemes.Count && graphemes [i + 1] == "\n")
                     {
-                        lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
+                        lines.Add (StringExtensions.ToString (graphemes.GetRange (start, end - start)));
                         i += 2;
                         start = i;
                     }
                     else
                     {
-                        lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
+                        lines.Add (StringExtensions.ToString (graphemes.GetRange (start, end - start)));
                         i++;
                         start = i;
                     }
@@ -1366,14 +1369,14 @@ public class TextFormatter
             }
         }
 
-        switch (runes.Count)
+        switch (graphemes.Count)
         {
             case > 0 when lines.Count == 0:
-                lines.Add (StringExtensions.ToString (runes));
+                lines.Add (StringExtensions.ToString (graphemes));
 
                 break;
-            case > 0 when start < runes.Count:
-                lines.Add (StringExtensions.ToString (runes.GetRange (start, runes.Count - start)));
+            case > 0 when start < graphemes.Count:
+                lines.Add (StringExtensions.ToString (graphemes.GetRange (start, graphemes.Count - start)));
 
                 break;
             default:
@@ -1401,16 +1404,19 @@ public class TextFormatter
         }
 
         // if value is not wide enough
-        if (text.EnumerateRunes ().Sum (c => c.GetColumns ()) < width)
+        string [] graphemes = GraphemeHelper.GetGraphemes (text).ToArray ();
+        int totalColumns = graphemes.Sum (s => s.GetColumns ());
+
+        if (totalColumns < width)
         {
             // pad it out with spaces to the given Alignment
-            int toPad = width - text.EnumerateRunes ().Sum (c => c.GetColumns ());
+            int toPad = width - totalColumns;
 
             return text + new string (' ', toPad);
         }
 
         // value is too wide
-        return new (text.TakeWhile (c => (width -= ((Rune)c).GetColumns ()) >= 0).ToArray ());
+        return string.Concat (graphemes.TakeWhile (t => (width -= t.GetColumns ()) >= 0));
     }
 
     /// <summary>Formats the provided text to fit within the width provided using word wrapping.</summary>
@@ -1451,18 +1457,18 @@ public class TextFormatter
             return lines;
         }
 
-        List<Rune> runes = StripCRLF (text).ToRuneList ();
+        List<string> graphemes = GraphemeHelper.GetGraphemes (StripCRLF (text)).ToList ();
 
         int start = Math.Max (
-                              !runes.Contains ((Rune)' ') && textFormatter is { VerticalAlignment: Alignment.End } && IsVerticalDirection (textDirection)
-                                  ? runes.Count - width
+                              !graphemes.Contains (" ") && textFormatter is { VerticalAlignment: Alignment.End } && IsVerticalDirection (textDirection)
+                                  ? graphemes.Count - width
                                   : 0,
                               0);
         int end;
 
         if (preserveTrailingSpaces)
         {
-            while ((end = start) < runes.Count)
+            while (start < graphemes.Count)
             {
                 end = GetNextWhiteSpace (start, width, out bool incomplete);
 
@@ -1473,7 +1479,7 @@ public class TextFormatter
                     break;
                 }
 
-                lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
+                lines.Add (StringExtensions.ToString (graphemes.GetRange (start, end - start)));
                 start = end;
 
                 if (incomplete)
@@ -1490,14 +1496,14 @@ public class TextFormatter
             {
                 while ((end = start
                               + GetLengthThatFits (
-                                                   runes.GetRange (start, runes.Count - start),
+                                                   string.Concat (graphemes.GetRange (start, graphemes.Count - start)),
                                                    width,
                                                    tabWidth,
                                                    textDirection
                                                   ))
-                       < runes.Count)
+                       < graphemes.Count)
                 {
-                    while (runes [end].Value != ' ' && end > start)
+                    while (graphemes [end] != " " && end > start)
                     {
                         end--;
                     }
@@ -1506,22 +1512,22 @@ public class TextFormatter
                     {
                         end = start
                               + GetLengthThatFits (
-                                                   runes.GetRange (end, runes.Count - end),
+                                                   string.Concat (graphemes.GetRange (end, graphemes.Count - end)),
                                                    width,
                                                    tabWidth,
                                                    textDirection
                                                   );
                     }
 
-                    var str = StringExtensions.ToString (runes.GetRange (start, end - start));
+                    var str = StringExtensions.ToString (graphemes.GetRange (start, end - start));
                     int zeroLength = text.EnumerateRunes ().Sum (r => r.GetColumns () == 0 ? 1 : 0);
 
-                    if (end > start && GetRuneWidth (str, tabWidth, textDirection) <= width + zeroLength)
+                    if (end > start && GetTextWidth (str, tabWidth, textDirection) <= width + zeroLength)
                     {
                         lines.Add (str);
                         start = end;
 
-                        if (runes [end].Value == ' ')
+                        if (graphemes [end] == " ")
                         {
                             start++;
                         }
@@ -1535,9 +1541,9 @@ public class TextFormatter
             }
             else
             {
-                while ((end = start + width) < runes.Count)
+                while ((end = start + width) < graphemes.Count)
                 {
-                    while (runes [end].Value != ' ' && end > start)
+                    while (graphemes [end] != " " && end > start)
                     {
                         end--;
                     }
@@ -1549,11 +1555,11 @@ public class TextFormatter
 
                     var zeroLength = 0;
 
-                    for (int i = end; i < runes.Count - start; i++)
+                    for (int i = end; i < graphemes.Count - start; i++)
                     {
-                        Rune r = runes [i];
+                        string s = graphemes [i];
 
-                        if (r.GetColumns () == 0)
+                        if (s.GetColumns () == 0)
                         {
                             zeroLength++;
                         }
@@ -1565,7 +1571,7 @@ public class TextFormatter
 
                     lines.Add (
                                StringExtensions.ToString (
-                                                          runes.GetRange (
+                                                          graphemes.GetRange (
                                                                           start,
                                                                           end - start + zeroLength
                                                                          )
@@ -1574,7 +1580,7 @@ public class TextFormatter
                     end += zeroLength;
                     start = end;
 
-                    if (runes [end].Value == ' ')
+                    if (graphemes [end] == " ")
                     {
                         start++;
                     }
@@ -1588,13 +1594,13 @@ public class TextFormatter
             int length = cLength;
             incomplete = false;
 
-            while (length < cWidth && to < runes.Count)
+            while (length < cWidth && to < graphemes.Count)
             {
-                Rune rune = runes [to];
+                string grapheme = graphemes [to];
 
                 if (IsHorizontalDirection (textDirection))
                 {
-                    length += rune.GetColumns ();
+                    length += grapheme.GetColumns (false);
                 }
                 else
                 {
@@ -1603,7 +1609,7 @@ public class TextFormatter
 
                 if (length > cWidth)
                 {
-                    if (to >= runes.Count || (length > 1 && cWidth <= 1))
+                    if (to >= graphemes.Count || (length > 1 && cWidth <= 1))
                     {
                         incomplete = true;
                     }
@@ -1611,15 +1617,15 @@ public class TextFormatter
                     return to;
                 }
 
-                switch (rune.Value)
+                switch (grapheme)
                 {
-                    case ' ' when length == cWidth:
+                    case " " when length == cWidth:
                         return to + 1;
-                    case ' ' when length > cWidth:
+                    case " " when length > cWidth:
                         return to;
-                    case ' ':
+                    case " ":
                         return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length);
-                    case '\t':
+                    case "\t":
                         {
                             length += tabWidth + 1;
 
@@ -1644,8 +1650,8 @@ public class TextFormatter
 
             return cLength switch
             {
-                > 0 when to < runes.Count && runes [to].Value != ' ' && runes [to].Value != '\t' => from,
-                > 0 when to < runes.Count && (runes [to].Value == ' ' || runes [to].Value == '\t') => from,
+                > 0 when to < graphemes.Count && graphemes [to] != " " && graphemes [to] != "\t" => from,
+                > 0 when to < graphemes.Count && (graphemes [to] == " " || graphemes [to] == "\t") => from,
                 _ => to
             };
         }
@@ -1653,7 +1659,7 @@ public class TextFormatter
         if (start < text.GetRuneCount ())
         {
             string str = ReplaceTABWithSpaces (
-                                               StringExtensions.ToString (runes.GetRange (start, runes.Count - start)),
+                                               StringExtensions.ToString (graphemes.GetRange (start, graphemes.Count - start)),
                                                tabWidth
                                               );
 
@@ -1717,42 +1723,42 @@ public class TextFormatter
         }
 
         text = ReplaceTABWithSpaces (text, tabWidth);
-        List<Rune> runes = text.ToRuneList ();
-        int zeroLength = runes.Sum (r => r.GetColumns () == 0 ? 1 : 0);
+        List<string> graphemes = GraphemeHelper.GetGraphemes (text).ToList ();
+        int zeroLength = graphemes.Sum (s => s.EnumerateRunes ().Sum (r => r.GetColumns() == 0 ? 1 : 0));
 
-        if (runes.Count - zeroLength > width)
+        if (graphemes.Count - zeroLength > width)
         {
             if (IsHorizontalDirection (textDirection))
             {
                 if (textFormatter is { Alignment: Alignment.End })
                 {
-                    return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection);
+                    return GetRangeThatFits (graphemes, graphemes.Count - width, text, width, tabWidth, textDirection);
                 }
 
                 if (textFormatter is { Alignment: Alignment.Center })
                 {
-                    return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
+                    return GetRangeThatFits (graphemes, Math.Max ((graphemes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
                 }
 
-                return GetRangeThatFits (runes, 0, text, width, tabWidth, textDirection);
+                return GetRangeThatFits (graphemes, 0, text, width, tabWidth, textDirection);
             }
 
             if (IsVerticalDirection (textDirection))
             {
                 if (textFormatter is { VerticalAlignment: Alignment.End })
                 {
-                    return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection);
+                    return GetRangeThatFits (graphemes, graphemes.Count - width, text, width, tabWidth, textDirection);
                 }
 
                 if (textFormatter is { VerticalAlignment: Alignment.Center })
                 {
-                    return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
+                    return GetRangeThatFits (graphemes, Math.Max ((graphemes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
                 }
 
-                return GetRangeThatFits (runes, 0, text, width, tabWidth, textDirection);
+                return GetRangeThatFits (graphemes, 0, text, width, tabWidth, textDirection);
             }
 
-            return StringExtensions.ToString (runes.GetRange (0, width + zeroLength));
+            return StringExtensions.ToString (graphemes.GetRange (0, width + zeroLength));
         }
 
         if (justify)
@@ -1764,18 +1770,18 @@ public class TextFormatter
         {
             if (textFormatter is { Alignment: Alignment.End })
             {
-                if (GetRuneWidth (text, tabWidth, textDirection) > width)
+                if (GetTextWidth (text, tabWidth, textDirection) > width)
                 {
-                    return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection);
+                    return GetRangeThatFits (graphemes, graphemes.Count - width, text, width, tabWidth, textDirection);
                 }
             }
             else if (textFormatter is { Alignment: Alignment.Center })
             {
-                return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
+                return GetRangeThatFits (graphemes, Math.Max ((graphemes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
             }
-            else if (GetRuneWidth (text, tabWidth, textDirection) > width)
+            else if (GetTextWidth (text, tabWidth, textDirection) > width)
             {
-                return GetRangeThatFits (runes, 0, text, width, tabWidth, textDirection);
+                return GetRangeThatFits (graphemes, 0, text, width, tabWidth, textDirection);
             }
         }
 
@@ -1783,28 +1789,28 @@ public class TextFormatter
         {
             if (textFormatter is { VerticalAlignment: Alignment.End })
             {
-                if (runes.Count - zeroLength > width)
+                if (graphemes.Count - zeroLength > width)
                 {
-                    return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection);
+                    return GetRangeThatFits (graphemes, graphemes.Count - width, text, width, tabWidth, textDirection);
                 }
             }
             else if (textFormatter is { VerticalAlignment: Alignment.Center })
             {
-                return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
+                return GetRangeThatFits (graphemes, Math.Max ((graphemes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
             }
-            else if (runes.Count - zeroLength > width)
+            else if (graphemes.Count - zeroLength > width)
             {
-                return GetRangeThatFits (runes, 0, text, width, tabWidth, textDirection);
+                return GetRangeThatFits (graphemes, 0, text, width, tabWidth, textDirection);
             }
         }
 
         return text;
     }
 
-    private static string GetRangeThatFits (List<Rune> runes, int index, string text, int width, int tabWidth, TextDirection textDirection)
+    private static string GetRangeThatFits (List<string> strings, int index, string text, int width, int tabWidth, TextDirection textDirection)
     {
         return StringExtensions.ToString (
-                                          runes.GetRange (
+                                          strings.GetRange (
                                                           Math.Max (index, 0),
                                                           GetLengthThatFits (text, width, tabWidth, textDirection)
                                                          )
@@ -1842,7 +1848,7 @@ public class TextFormatter
 
         if (IsHorizontalDirection (textDirection))
         {
-            textCount = words.Sum (arg => GetRuneWidth (arg, tabWidth, textDirection));
+            textCount = words.Sum (arg => GetTextWidth (arg, tabWidth, textDirection));
         }
         else
         {
@@ -2137,11 +2143,11 @@ public class TextFormatter
              i < (linesCount == -1 ? lines.Count : startLine + linesCount);
              i++)
         {
-            string runes = lines [i];
+            string strings = lines [i];
 
-            if (runes.Length > 0)
+            if (strings.Length > 0)
             {
-                max += runes.EnumerateRunes ().Max (r => GetRuneWidth (r, tabWidth));
+                max += strings.EnumerateRunes ().Max (r => GetRuneWidth (r, tabWidth));
             }
         }
 
@@ -2163,7 +2169,7 @@ public class TextFormatter
     {
         List<string> result = SplitNewLine (text);
 
-        return result.Max (x => GetRuneWidth (x, tabWidth));
+        return result.Max (x => GetTextWidth (x, tabWidth));
     }
 
     /// <summary>
@@ -2182,13 +2188,13 @@ public class TextFormatter
     public static int GetSumMaxCharWidth (string text, int startIndex = -1, int length = -1, int tabWidth = 0)
     {
         var max = 0;
-        Rune [] runes = text.ToRunes ();
+        string [] graphemes = GraphemeHelper.GetGraphemes (text).ToArray ();
 
         for (int i = startIndex == -1 ? 0 : startIndex;
-             i < (length == -1 ? runes.Length : startIndex + length);
+             i < (length == -1 ? graphemes.Length : startIndex + length);
              i++)
         {
-            max += GetRuneWidth (runes [i], tabWidth);
+            max += GetStringWidth (graphemes [i], tabWidth);
         }
 
         return max;
@@ -2206,51 +2212,38 @@ public class TextFormatter
     /// <returns>The index of the text that fit the width.</returns>
     public static int GetLengthThatFits (string text, int width, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
     {
-        return GetLengthThatFits (text?.ToRuneList () ?? [], width, tabWidth, textDirection);
-    }
-
-    /// <summary>Gets the number of the Runes in a list of Runes that will fit in <paramref name="width"/>.</summary>
-    /// <remarks>
-    ///     This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding
-    ///     glyphs (e.g. Arabic).
-    /// </remarks>
-    /// <param name="runes">The list of runes.</param>
-    /// <param name="width">The width.</param>
-    /// <param name="tabWidth">The width used for a tab.</param>
-    /// <param name="textDirection">The text direction.</param>
-    /// <returns>The index of the last Rune in <paramref name="runes"/> that fit in <paramref name="width"/>.</returns>
-    public static int GetLengthThatFits (List<Rune> runes, int width, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
-    {
-        if (runes is null || runes.Count == 0)
+        if (string.IsNullOrEmpty (text))
         {
             return 0;
         }
 
-        var runesLength = 0;
-        var runeIdx = 0;
+        var textLength = 0;
+        var stringIdx = 0;
 
-        for (; runeIdx < runes.Count; runeIdx++)
+        foreach (string grapheme in GraphemeHelper.GetGraphemes (text))
         {
-            int runeWidth = GetRuneWidth (runes [runeIdx], tabWidth, textDirection);
+            int textWidth = GetStringWidth (grapheme, tabWidth, textDirection);
 
-            if (runesLength + runeWidth > width)
+            if (textLength + textWidth > width)
             {
                 break;
             }
 
-            runesLength += runeWidth;
+            textLength += textWidth;
+            stringIdx++;
         }
 
-        return runeIdx;
+        return stringIdx;
     }
 
-    private static int GetRuneWidth (string str, int tabWidth, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
+    private static int GetTextWidth (string str, int tabWidth, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
     {
         int runesWidth = 0;
-        foreach (Rune rune in str.EnumerateRunes ())
+        foreach (string grapheme in GraphemeHelper.GetGraphemes (str))
         {
-            runesWidth += GetRuneWidth (rune, tabWidth, textDirection);
+            runesWidth += GetStringWidth (grapheme, tabWidth, textDirection);
         }
+
         return runesWidth;
     }
 
@@ -2271,6 +2264,23 @@ public class TextFormatter
         return runeWidth;
     }
 
+    private static int GetStringWidth (string str, int tabWidth, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
+    {
+        int textWidth = IsHorizontalDirection (textDirection) ? str.GetColumns (false) : str.GetColumns () == 0 ? 0 : 1;
+
+        if (str == "\t")
+        {
+            return tabWidth;
+        }
+
+        if (textWidth is < 0 or > 0)
+        {
+            return Math.Max (textWidth, 1);
+        }
+
+        return textWidth;
+    }
+
     /// <summary>Gets the index position from the list based on the <paramref name="width"/>.</summary>
     /// <remarks>
     ///     This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding
@@ -2282,23 +2292,23 @@ public class TextFormatter
     /// <returns>The index of the list that fit the width.</returns>
     public static int GetMaxColsForWidth (List<string> lines, int width, int tabWidth = 0)
     {
-        var runesLength = 0;
+        var textLength = 0;
         var lineIdx = 0;
 
         for (; lineIdx < lines.Count; lineIdx++)
         {
-            List<Rune> runes = lines [lineIdx].ToRuneList ();
+            string [] graphemes = GraphemeHelper.GetGraphemes (lines [lineIdx]).ToArray ();
 
-            int maxRruneWidth = runes.Count > 0
-                                    ? runes.Max (r => GetRuneWidth (r, tabWidth))
+            int maxTextWidth = graphemes.Length > 0
+                                    ? graphemes.Max (r => GetStringWidth (r, tabWidth))
                                     : 1;
 
-            if (runesLength + maxRruneWidth > width)
+            if (textLength + maxTextWidth > width)
             {
                 break;
             }
 
-            runesLength += maxRruneWidth;
+            textLength += maxTextWidth;
         }
 
         return lineIdx;

+ 1 - 1
Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs

@@ -657,7 +657,7 @@ public partial class Border
         if (Parent!.SuperView is null)
         {
             // Redraw the entire app window.
-            App?.Current?.SetNeedsDraw ();
+            App?.TopRunnable?.SetNeedsDraw ();
         }
         else
         {

+ 4 - 4
Terminal.Gui/ViewBase/Adornment/ShadowView.cs

@@ -100,7 +100,7 @@ internal class ShadowView : View
 
                 if (c < ScreenContents?.GetLength (1) && r < ScreenContents?.GetLength (0))
                 {
-                    AddRune (ScreenContents [r, c].Rune);
+                    AddStr (ScreenContents [r, c].Grapheme);
                 }
             }
         }
@@ -134,7 +134,7 @@ internal class ShadowView : View
 
                 if (ScreenContents is { } && screen.X < ScreenContents.GetLength (1) && r < ScreenContents.GetLength (0))
                 {
-                    AddRune (ScreenContents [r, c].Rune);
+                    AddStr (ScreenContents [r, c].Grapheme);
                 }
             }
         }
@@ -142,7 +142,7 @@ internal class ShadowView : View
 
     private Attribute GetAttributeUnderLocation (Point location)
     {
-        if (SuperView is not Adornment adornment
+        if (SuperView is not Adornment
             || location.X < 0
             || location.X >= App?.Screen.Width
             || location.Y < 0
@@ -171,7 +171,7 @@ internal class ShadowView : View
         if (newAttribute.Background == Color.DarkGray)
         {
             List<View?> currentViewsUnderMouse = GetViewsUnderLocation (location, ViewportSettingsFlags.Transparent);
-            View? underView = currentViewsUnderMouse!.LastOrDefault ();
+            View? underView = currentViewsUnderMouse.LastOrDefault ();
             attr = underView?.GetAttributeForRole (VisualRole.Normal) ?? Attribute.Default;
 
             newAttribute = new (

+ 19 - 1
Terminal.Gui/ViewBase/View.Drawing.Primitives.cs

@@ -32,7 +32,6 @@ public partial class View
         Driver?.AddRune (rune);
     }
 
-
     /// <summary>
     ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
     ///     convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
@@ -72,6 +71,25 @@ public partial class View
     {
         Driver?.AddStr (str);
     }
+
+    /// <summary>Draws the specified <paramref name="str"/> in the specified viewport-relative column and row of the View.</summary>
+    /// <para>
+    ///     If the provided coordinates are outside the visible content area, this method does nothing.
+    /// </para>
+    /// <remarks>
+    ///     The top-left corner of the visible content area is <c>ViewPort.Location</c>.
+    /// </remarks>
+    /// <param name="col">Column (viewport-relative).</param>
+    /// <param name="row">Row (viewport-relative).</param>
+    /// <param name="str">The Text.</param>
+    public void AddStr (int col, int row, string str)
+    {
+        if (Move (col, row))
+        {
+            Driver?.AddStr (str);
+        }
+    }
+
     /// <summary>Utility function to draw strings that contain a hotkey.</summary>
     /// <param name="text">String to display, the hotkey specifier before a letter flags the next letter as the hotkey.</param>
     /// <param name="hotColor">Hot color.</param>

+ 7 - 7
Terminal.Gui/ViewBase/View.Drawing.cs

@@ -194,11 +194,11 @@ public partial class View // Drawing APIs
         else
         {
             // Set the clip to be just the thicknesses of the adornments
-            // TODO: Put this union logic in a method on View? 
+            // TODO: Put this union logic in a method on View?
             Region? clipAdornments = Margin!.Thickness.AsRegion (Margin!.FrameToScreen ());
-            clipAdornments?.Combine (Border!.Thickness.AsRegion (Border!.FrameToScreen ()), RegionOp.Union);
-            clipAdornments?.Combine (Padding!.Thickness.AsRegion (Padding!.FrameToScreen ()), RegionOp.Union);
-            clipAdornments?.Combine (originalClip, RegionOp.Intersect);
+            clipAdornments.Combine (Border!.Thickness.AsRegion (Border!.FrameToScreen ()), RegionOp.Union);
+            clipAdornments.Combine (Padding!.Thickness.AsRegion (Padding!.FrameToScreen ()), RegionOp.Union);
+            clipAdornments.Combine (originalClip, RegionOp.Intersect);
             SetClip (clipAdornments);
         }
 
@@ -239,7 +239,7 @@ public partial class View // Drawing APIs
     {
         // We do not attempt to draw Margin. It is drawn in a separate pass.
 
-        // Each of these renders lines to this View's LineCanvas 
+        // Each of these renders lines to this View's LineCanvas
         // Those lines will be finally rendered in OnRenderLineCanvas
         if (Border is { } && Border.Thickness != Thickness.Empty)
         {
@@ -660,7 +660,7 @@ public partial class View // Drawing APIs
                     Driver.Move (p.Key.X, p.Key.Y);
 
                     // TODO: #2616 - Support combining sequences that don't normalize
-                    AddRune (p.Value.Value.Rune);
+                    AddStr (p.Value.Value.Grapheme);
                 }
             }
 
@@ -687,7 +687,7 @@ public partial class View // Drawing APIs
                 context!.ClipDrawnRegion (ViewportToScreen (Viewport));
 
                 // Exclude the drawn region from the clip
-                ExcludeFromClip (context!.GetDrawnRegion ());
+                ExcludeFromClip (context.GetDrawnRegion ());
 
                 // Exclude the Border and Padding from the clip
                 ExcludeFromClip (Border?.Thickness.AsRegion (Border.FrameToScreen ()));

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

@@ -362,12 +362,12 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
 
     #endregion AddRemove
 
-    // TODO: This drives a weird coupling of Application.Current and View. It's not clear why this is needed.
+    // TODO: This drives a weird coupling of Application.TopRunnable and View. It's not clear why this is needed.
     /// <summary>Get the top superview of a given <see cref="View"/>.</summary>
     /// <returns>The superview view.</returns>
     internal View? GetTopSuperView (View? view = null, View? superview = null)
     {
-        View? top = superview ?? App?.Current;
+        View? top = superview ?? App?.TopRunnable;
 
         for (View? v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
         {

+ 13 - 13
Terminal.Gui/ViewBase/View.Layout.cs

@@ -437,7 +437,7 @@ public partial class View // Layout APIs
 
     private void NeedsClearScreenNextIteration ()
     {
-        if (App is { Current: { } } && App.Current == this && App.SessionStack.Count == 1)
+        if (App is { TopRunnable: { } } && App.TopRunnable == this && App.SessionStack.Count == 1)
         {
             // If this is the only TopLevel, we need to redraw the screen
             App.ClearScreenNextIteration = true;
@@ -1113,8 +1113,8 @@ public partial class View // Layout APIs
     {
         // TODO: Get rid of refs to Top
         Size superViewContentSize = SuperView?.GetContentSize ()
-                                    ?? (App?.Current is { } && App?.Current != this && App!.Current.IsInitialized
-                                            ? App.Current.GetContentSize ()
+                                    ?? (App?.TopRunnable is { } && App?.TopRunnable != this && App!.TopRunnable.IsInitialized
+                                            ? App.TopRunnable.GetContentSize ()
                                             : App?.Screen.Size ?? new (2048, 2048));
 
         return superViewContentSize;
@@ -1130,7 +1130,7 @@ public partial class View // Layout APIs
     /// </summary>
     /// <remarks>
     ///     If <paramref name="viewToMove"/> does not have a <see cref="View.SuperView"/> or it's SuperView is not
-    ///     <see cref="IApplication.Current"/> the position will be bound by  <see cref="IApplication.Screen"/>.
+    ///     <see cref="IApplication.TopRunnable"/> the position will be bound by  <see cref="IApplication.Screen"/>.
     /// </remarks>
     /// <param name="viewToMove">The View that is to be moved.</param>
     /// <param name="targetX">The target x location.</param>
@@ -1138,7 +1138,7 @@ public partial class View // Layout APIs
     /// <param name="nx">The new x location that will ensure <paramref name="viewToMove"/> will be fully visible.</param>
     /// <param name="ny">The new y location that will ensure <paramref name="viewToMove"/> will be fully visible.</param>
     /// <returns>
-    ///     Either <see cref="IApplication.Current"/> (if <paramref name="viewToMove"/> does not have a Super View) or
+    ///     Either <see cref="IApplication.TopRunnable"/> (if <paramref name="viewToMove"/> does not have a Super View) or
     ///     <paramref name="viewToMove"/>'s SuperView. This can be used to ensure LayoutSubViews is called on the correct View.
     /// </returns>
     internal static View? GetLocationEnsuringFullVisibility (
@@ -1154,10 +1154,10 @@ public partial class View // Layout APIs
 
         IApplication? app = viewToMove.App;
 
-        if (viewToMove?.SuperView is null || viewToMove == app?.Current || viewToMove?.SuperView == app?.Current)
+        if (viewToMove?.SuperView is null || viewToMove == app?.TopRunnable || viewToMove?.SuperView == app?.TopRunnable)
         {
             maxDimension = app?.Screen.Width ?? 0;
-            superView = app?.Current;
+            superView = app?.TopRunnable;
         }
         else
         {
@@ -1190,9 +1190,9 @@ public partial class View // Layout APIs
         var menuVisible = false;
         var statusVisible = false;
 
-        if (viewToMove?.SuperView is null || viewToMove == app?.Current || viewToMove?.SuperView == app?.Current)
+        if (viewToMove?.SuperView is null || viewToMove == app?.TopRunnable || viewToMove?.SuperView == app?.TopRunnable)
         {
-            menuVisible = app?.Current?.MenuBar?.Visible == true;
+            menuVisible = app?.TopRunnable?.MenuBar?.Visible == true;
         }
         else
         {
@@ -1209,7 +1209,7 @@ public partial class View // Layout APIs
             }
         }
 
-        if (viewToMove?.SuperView is null || viewToMove == app?.Current || viewToMove?.SuperView == app?.Current)
+        if (viewToMove?.SuperView is null || viewToMove == app?.TopRunnable || viewToMove?.SuperView == app?.TopRunnable)
         {
             maxDimension = menuVisible ? 1 : 0;
         }
@@ -1220,7 +1220,7 @@ public partial class View // Layout APIs
 
         ny = Math.Max (targetY, maxDimension);
 
-        if (viewToMove?.SuperView is null || viewToMove == app?.Current || viewToMove?.SuperView == app?.Current)
+        if (viewToMove?.SuperView is null || viewToMove == app?.TopRunnable || viewToMove?.SuperView == app?.TopRunnable)
         {
             if (app is { })
             {
@@ -1310,7 +1310,7 @@ public partial class View // Layout APIs
                     }
                 }
 
-                if (toplevel == App.Current)
+                if (toplevel == App.TopRunnable)
                 {
                     checkedTop = true;
                 }
@@ -1318,7 +1318,7 @@ public partial class View // Layout APIs
         }
 
         // Fallback: If TopLevels is empty or Top is not in TopLevels, check Top directly (for test compatibility)
-        if (!checkedTop && App?.Current is { Visible: true } top)
+        if (!checkedTop && App?.TopRunnable is { Visible: true } top)
         {
             // For root toplevels, allow hit-testing even if location is outside bounds (for drag/move)
             List<View?> result = GetViewsUnderLocation (top, screenLocation, excludeViewportSettingsFlags);

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

@@ -395,7 +395,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
     public event EventHandler<HasFocusEventArgs>? FocusedChanged;
 
     /// <summary>Returns a value indicating if this View is currently on Top (Active)</summary>
-    public bool IsCurrentTop => App?.Current == this;
+    public bool IsCurrentTop => App?.TopRunnable == this;
 
     /// <summary>
     ///     Returns the most focused SubView down the subview-hierarchy.
@@ -853,18 +853,18 @@ public partial class View // Focus and cross-view navigation management (TabStop
                 }
             }
 
-            // Application.Current?
-            if (newFocusedView is null && App?.Current is { CanFocus: true, HasFocus: false })
+            // Application.TopRunnable?
+            if (newFocusedView is null && App?.TopRunnable is { CanFocus: true, HasFocus: false })
             {
                 // Temporarily ensure this view can't get focus
                 bool prevCanFocus = _canFocus;
                 _canFocus = false;
-                bool restoredFocus = App?.Current.RestoreFocus () ?? false;
+                bool restoredFocus = App?.TopRunnable.RestoreFocus () ?? false;
                 _canFocus = prevCanFocus;
 
-                if (App?.Current is { CanFocus: true, HasFocus: true })
+                if (App?.TopRunnable is { CanFocus: true, HasFocus: true })
                 {
-                    newFocusedView = App?.Current;
+                    newFocusedView = App?.TopRunnable;
                 }
                 else if (restoredFocus)
                 {

+ 13 - 13
Terminal.Gui/Views/Autocomplete/AutocompleteFilepathContext.cs

@@ -12,16 +12,16 @@ internal class AutocompleteFilepathContext (string currentLine, int cursorPositi
 
 internal class FilepathSuggestionGenerator : ISuggestionGenerator
 {
-    private FileDialogState state;
+    private FileDialogState _state;
 
     public IEnumerable<Suggestion> GenerateSuggestions (AutocompleteContext context)
     {
         if (context is AutocompleteFilepathContext fileState)
         {
-            state = fileState.State;
+            _state = fileState.State;
         }
 
-        if (state is null)
+        if (_state is null)
         {
             return Enumerable.Empty<Suggestion> ();
         }
@@ -42,7 +42,7 @@ internal class FilepathSuggestionGenerator : ISuggestionGenerator
             return Enumerable.Empty<Suggestion> ();
         }
 
-        if (term.Equals (state?.Directory?.Name))
+        if (term.Equals (_state?.Directory?.Name))
         {
             // Clear suggestions
             return Enumerable.Empty<Suggestion> ();
@@ -50,13 +50,13 @@ internal class FilepathSuggestionGenerator : ISuggestionGenerator
 
         bool isWindows = RuntimeInformation.IsOSPlatform (OSPlatform.Windows);
 
-        string [] suggestions = state.Children.Where (d => !d.IsParent)
-                                     .Select (
-                                              e => e.FileSystemInfo is IDirectoryInfo d
-                                                       ? d.Name + Path.DirectorySeparatorChar
-                                                       : e.FileSystemInfo.Name
-                                             )
-                                     .ToArray ();
+        string [] suggestions = _state!.Children.Where (d => !d.IsParent)
+                                       .Select (
+                                                e => e.FileSystemInfo is IDirectoryInfo d
+                                                         ? d.Name + Path.DirectorySeparatorChar
+                                                         : e.FileSystemInfo.Name
+                                               )
+                                       .ToArray ();
 
         string [] validSuggestions = suggestions
                                      .Where (
@@ -82,9 +82,9 @@ internal class FilepathSuggestionGenerator : ISuggestionGenerator
                                .ToList ();
     }
 
-    public bool IsWordChar (Rune rune)
+    public bool IsWordChar (string text)
     {
-        if (rune.Value == '\n')
+        if (text == "\n")
         {
             return false;
         }

+ 2 - 2
Terminal.Gui/Views/Autocomplete/ISuggestionGenerator.cs

@@ -8,9 +8,9 @@ public interface ISuggestionGenerator
     IEnumerable<Suggestion> GenerateSuggestions (AutocompleteContext context);
 
     /// <summary>
-    ///     Returns <see langword="true"/> if <paramref name="rune"/> is a character that would continue autocomplete
+    ///     Returns <see langword="true"/> if <paramref name="text"/> is a character that would continue autocomplete
     ///     suggesting. Returns <see langword="false"/> if it is a 'breaking' character (i.e. terminating current word
     ///     boundary)
     /// </summary>
-    bool IsWordChar (Rune rune);
+    bool IsWordChar (string text);
 }

+ 1 - 1
Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs

@@ -188,7 +188,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
     /// <returns><c>true</c>if the key can be handled <c>false</c>otherwise.</returns>
     public override bool ProcessKey (Key key)
     {
-        if (SuggestionGenerator.IsWordChar ((Rune)key))
+        if (SuggestionGenerator.IsWordChar (key.AsRune.ToString ()))
         {
             Visible = true;
             _closed = false;

+ 10 - 6
Terminal.Gui/Views/Autocomplete/SingleWordSuggestionGenerator.cs

@@ -18,10 +18,10 @@ public class SingleWordSuggestionGenerator : ISuggestionGenerator
         // if there is nothing to pick from
         if (AllSuggestions.Count == 0)
         {
-            return Enumerable.Empty<Suggestion> ();
+            return [];
         }
 
-        List<Rune> line = context.CurrentLine.Select (c => c.Rune).ToList ();
+        List<string> line = context.CurrentLine.Select (c => c.Grapheme).ToList ();
         string currentWord = IdxToWord (line, context.CursorPosition, out int startIdx);
         context.CursorPosition = startIdx < 1 ? startIdx : Math.Min (startIdx + 1, line.Count);
 
@@ -44,9 +44,13 @@ public class SingleWordSuggestionGenerator : ISuggestionGenerator
     ///     Return true if the given symbol should be considered part of a word and can be contained in matches. Base
     ///     behavior is to use <see cref="char.IsLetterOrDigit(char)"/>
     /// </summary>
-    /// <param name="rune">The rune.</param>
+    /// <param name="text">The text.</param>
     /// <returns></returns>
-    public virtual bool IsWordChar (Rune rune) { return char.IsLetterOrDigit ((char)rune.Value); }
+    public virtual bool IsWordChar (string text)
+    {
+        return !string.IsNullOrEmpty (text)
+               && Rune.IsLetterOrDigit (text.EnumerateRunes ().First ());
+    }
 
     /// <summary>
     ///     <para>
@@ -65,7 +69,7 @@ public class SingleWordSuggestionGenerator : ISuggestionGenerator
     /// <param name="startIdx">The start index of the word.</param>
     /// <param name="columnOffset"></param>
     /// <returns></returns>
-    protected virtual string IdxToWord (List<Rune> line, int idx, out int startIdx, int columnOffset = 0)
+    protected virtual string IdxToWord (List<string> line, int idx, out int startIdx, int columnOffset = 0)
     {
         var sb = new StringBuilder ();
         startIdx = idx;
@@ -94,7 +98,7 @@ public class SingleWordSuggestionGenerator : ISuggestionGenerator
         {
             if (IsWordChar (line [startIdx]))
             {
-                sb.Insert (0, (char)line [startIdx].Value);
+                sb.Insert (0, line [startIdx]);
             }
             else
             {

+ 25 - 54
Terminal.Gui/Views/CharMap/CharMap.cs

@@ -147,10 +147,7 @@ public class CharMap : View, IDesignable
                     break;
                 }
 
-                var rune = new Rune (cp);
-                Span<char> utf16 = new char [2];
-                rune.EncodeToUtf16 (utf16);
-                UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory (utf16 [0]);
+                UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory (cp);
                 if (cat == ShowUnicodeCategory.Value)
                 {
                     anyVisible = true;
@@ -684,7 +681,7 @@ public class CharMap : View, IDesignable
                 // Don't render out-of-range scalars
                 if (scalar > MAX_CODE_POINT)
                 {
-                    AddRune (' ');
+                    AddStr (" ");
                     if (visibleRow == selectedRowIndex && col == selectedCol)
                     {
                         SetAttributeForRole (VisualRole.Normal);
@@ -692,22 +689,20 @@ public class CharMap : View, IDesignable
                     continue;
                 }
 
-                var rune = (Rune)'?';
+                string grapheme = "?";
 
                 if (Rune.IsValid (scalar))
                 {
-                    rune = new (scalar);
+                    grapheme = new Rune (scalar).ToString ();
                 }
 
-                int width = rune.GetColumns ();
+                int width = grapheme.GetColumns ();
 
                 // Compute visibility based on ShowUnicodeCategory
                 bool isVisible = Rune.IsValid (scalar);
                 if (isVisible && ShowUnicodeCategory.HasValue)
                 {
-                    Span<char> filterUtf16 = new char [2];
-                    rune.EncodeToUtf16 (filterUtf16);
-                    UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory (filterUtf16 [0]);
+                    UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory (scalar);
                     isVisible = cat == ShowUnicodeCategory.Value;
                 }
 
@@ -716,11 +711,11 @@ public class CharMap : View, IDesignable
                     // Glyph row
                     if (isVisible)
                     {
-                        RenderRune (rune, width);
+                        RenderGrapheme (grapheme, width, scalar);
                     }
                     else
                     {
-                        AddRune (' ');
+                        AddStr (" ");
                     }
                 }
                 else
@@ -735,7 +730,7 @@ public class CharMap : View, IDesignable
                     }
                     else
                     {
-                        AddRune (' ');
+                        AddStr (" ");
                     }
                 }
 
@@ -749,21 +744,18 @@ public class CharMap : View, IDesignable
 
         return true;
 
-        void RenderRune (Rune rune, int width)
+        void RenderGrapheme (string grapheme, int width, int scalar)
         {
             // Get the UnicodeCategory
-            Span<char> utf16 = new char [2];
-            int charCount = rune.EncodeToUtf16 (utf16);
-
             // Get the bidi class for the first code unit
             // For most bidi characters, the first code unit is sufficient
-            UnicodeCategory category = CharUnicodeInfo.GetUnicodeCategory (utf16 [0]);
+            UnicodeCategory category = CharUnicodeInfo.GetUnicodeCategory (scalar);
 
             switch (category)
             {
                 case UnicodeCategory.OtherNotAssigned:
                     SetAttributeForRole (VisualRole.Highlight);
-                    AddRune (Rune.ReplacementChar);
+                    AddStr (Rune.ReplacementChar.ToString ());
                     SetAttributeForRole (VisualRole.Normal);
 
                     break;
@@ -772,7 +764,7 @@ public class CharMap : View, IDesignable
                 // These report width of 0 and don't render on their own.
                 case UnicodeCategory.Format:
                     SetAttributeForRole (VisualRole.Highlight);
-                    AddRune ('F');
+                    AddStr ("F");
                     SetAttributeForRole (VisualRole.Normal);
 
                     break;
@@ -785,36 +777,7 @@ public class CharMap : View, IDesignable
                 case UnicodeCategory.EnclosingMark:
                     if (width > 0)
                     {
-                        AddRune (rune);
-                    }
-                    else
-                    {
-                        if (rune.IsCombiningMark ())
-                        {
-                            // This is a hack to work around the fact that combining marks
-                            // a) can't be rendered on their own
-                            // b) that don't normalize are not properly supported in 
-                            //    any known terminal (esp Windows/AtlasEngine). 
-                            // See Issue #2616
-                            var sb = new StringBuilder ();
-                            sb.Append ('a');
-                            sb.Append (rune);
-
-                            // Try normalizing after combining with 'a'. If it normalizes, at least 
-                            // it'll show on the 'a'. If not, just show the replacement char.
-                            string normal = sb.ToString ().Normalize (NormalizationForm.FormC);
-
-                            if (normal.Length == 1)
-                            {
-                                AddRune ((Rune)normal [0]);
-                            }
-                            else
-                            {
-                                SetAttributeForRole (VisualRole.Highlight);
-                                AddRune ('M');
-                                SetAttributeForRole (VisualRole.Normal);
-                            }
-                        }
+                        AddStr (grapheme);
                     }
 
                     break;
@@ -824,20 +787,28 @@ public class CharMap : View, IDesignable
                 case UnicodeCategory.LineSeparator:
                 case UnicodeCategory.ParagraphSeparator:
                 case UnicodeCategory.Surrogate:
-                    AddRune (rune);
+                    AddStr (grapheme);
 
                     break;
+                case UnicodeCategory.OtherLetter:
+                    AddStr (grapheme);
 
+                    if (width == 0)
+                    {
+                        AddStr (" ");
+                    }
+
+                    break;
                 default:
 
                     // Draw the rune
                     if (width > 0)
                     {
-                        AddRune (rune);
+                        AddStr (grapheme);
                     }
                     else
                     {
-                        throw new InvalidOperationException ($"The Rune \"{rune}\" (U+{rune.Value:x6}) has zero width and no special-case UnicodeCategory logic applies.");
+                        throw new InvalidOperationException ($"The Rune \"{grapheme}\" (U+{Rune.GetRuneAt (grapheme, 0).Value:x6}) has zero width and no special-case UnicodeCategory logic applies.");
                     }
 
                     break;

+ 1 - 1
Terminal.Gui/Views/Color/ColorBar.cs

@@ -15,7 +15,7 @@ internal abstract class ColorBar : View, IColorBar
     /// </summary>
     protected ColorBar ()
     {
-        Height = 1;
+        Height = Dim.Auto(minimumContentDim: 1);
         Width = Dim.Fill ();
         CanFocus = true;
 

+ 3 - 2
Terminal.Gui/Views/Color/HueBar.cs

@@ -1,10 +1,11 @@
-
-
 using ColorHelper;
 using ColorConverter = ColorHelper.ColorConverter;
 
 namespace Terminal.Gui.Views;
 
+/// <summary>
+///     A bar representing the Hue component of a <see cref="Color"/> in HSL color space.
+/// </summary>
 internal class HueBar : ColorBar
 {
     /// <inheritdoc/>

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

@@ -21,7 +21,7 @@ public class Dialog : Window
     /// <remarks>
     ///     By default, <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and <see cref="View.Height"/> are
     ///     set
-    ///     such that the <see cref="Dialog"/> will be centered in, and no larger than 90% of <see cref="IApplication.Current"/>, if
+    ///     such that the <see cref="Dialog"/> will be centered in, and no larger than 90% of <see cref="IApplication.TopRunnable"/>, if
     ///     there is one. Otherwise,
     ///     it will be bound by the screen dimensions.
     /// </remarks>

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

@@ -12,10 +12,10 @@ internal sealed class Menu : View
 {
     public Menu ()
     {
-        if (Application.Current is { })
+        if (Application.TopRunnable is { })
         {
-            Application.Current.DrawComplete += Top_DrawComplete;
-            Application.Current.SizeChanging += Current_TerminalResized;
+            Application.TopRunnable.DrawComplete += Top_DrawComplete;
+            Application.TopRunnable.SizeChanging += Current_TerminalResized;
         }
 
         Application.MouseEvent += Application_RootMouseEvent;
@@ -231,10 +231,10 @@ internal sealed class Menu : View
     {
         RemoveKeyBindingsHotKey (_barItems);
 
-        if (Application.Current is { })
+        if (Application.TopRunnable is { })
         {
-            Application.Current.DrawComplete -= Top_DrawComplete;
-            Application.Current.SizeChanging -= Current_TerminalResized;
+            Application.TopRunnable.DrawComplete -= Top_DrawComplete;
+            Application.TopRunnable.SizeChanging -= Current_TerminalResized;
         }
 
         Application.MouseEvent -= Application_RootMouseEvent;

+ 4 - 4
Terminal.Gui/Views/Menuv1/MenuBar.cs

@@ -421,7 +421,7 @@ public class MenuBar : View, IDesignable
         _selected = 0;
         SetNeedsDraw ();
 
-        _previousFocused = (SuperView is null ? Application.Current?.Focused : SuperView.Focused)!;
+        _previousFocused = (SuperView is null ? Application.TopRunnable?.Focused : SuperView.Focused)!;
         OpenMenu (_selected);
 
         if (!SelectEnabledItem (
@@ -490,7 +490,7 @@ public class MenuBar : View, IDesignable
 
         if (_openMenu is null)
         {
-            _previousFocused = (SuperView is null ? Application.Current?.Focused ?? null : SuperView.Focused)!;
+            _previousFocused = (SuperView is null ? Application.TopRunnable?.Focused ?? null : SuperView.Focused)!;
         }
 
         OpenMenu (idx, sIdx, subMenu);
@@ -702,7 +702,7 @@ public class MenuBar : View, IDesignable
         }
 
         Rectangle superViewFrame = SuperView?.Frame ?? Application.Screen;
-        View? sv = SuperView ?? Application.Current;
+        View? sv = SuperView ?? Application.TopRunnable;
 
         if (sv is null)
         {
@@ -834,7 +834,7 @@ public class MenuBar : View, IDesignable
         {
             case null:
                 // Open a submenu below a MenuBar
-                _lastFocused ??= SuperView is null ? Application.Current?.MostFocused : SuperView.MostFocused;
+                _lastFocused ??= SuperView is null ? Application.TopRunnable?.MostFocused : SuperView.MostFocused;
 
                 if (_openSubMenu is { } && !CloseMenu (false, true))
                 {

+ 25 - 25
Terminal.Gui/Views/Slider/Slider.cs

@@ -74,13 +74,13 @@ public class Slider<T> : View, IOrientation
         switch (_config._sliderOrientation)
         {
             case Orientation.Horizontal:
-                Style.SpaceChar = new () { Rune = Glyphs.HLine }; // '─'
-                Style.OptionChar = new () { Rune = Glyphs.BlackCircle }; // '┼●🗹□⏹'
+                Style.SpaceChar = new () { Grapheme = Glyphs.HLine.ToString () }; // '─'
+                Style.OptionChar = new () { Grapheme = Glyphs.BlackCircle.ToString () }; // '┼●🗹□⏹'
 
                 break;
             case Orientation.Vertical:
-                Style.SpaceChar = new () { Rune = Glyphs.VLine };
-                Style.OptionChar = new () { Rune = Glyphs.BlackCircle };
+                Style.SpaceChar = new () { Grapheme = Glyphs.VLine.ToString () };
+                Style.OptionChar = new () { Grapheme = Glyphs.BlackCircle.ToString () };
 
                 break;
         }
@@ -105,12 +105,12 @@ public class Slider<T> : View, IOrientation
         */
 
         _config._legendsOrientation = _config._sliderOrientation;
-        Style.EmptyChar = new () { Rune = new (' ') };
-        Style.SetChar = new () { Rune = Glyphs.ContinuousMeterSegment }; // ■
-        Style.RangeChar = new () { Rune = Glyphs.Stipple }; // ░ ▒ ▓   // Medium shade not blinking on curses.
-        Style.StartRangeChar = new () { Rune = Glyphs.ContinuousMeterSegment };
-        Style.EndRangeChar = new () { Rune = Glyphs.ContinuousMeterSegment };
-        Style.DragChar = new () { Rune = Glyphs.Diamond };
+        Style.EmptyChar = new () { Grapheme = " " };
+        Style.SetChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () }; // ■
+        Style.RangeChar = new () { Grapheme = Glyphs.Stipple.ToString () }; // ░ ▒ ▓   // Medium shade not blinking on curses.
+        Style.StartRangeChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () };
+        Style.EndRangeChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () };
+        Style.DragChar = new () { Grapheme = Glyphs.Diamond.ToString () };
 
         // TODO: Support left & right (top/bottom)
         // First = '├',
@@ -256,11 +256,11 @@ public class Slider<T> : View, IOrientation
         switch (_config._sliderOrientation)
         {
             case Orientation.Horizontal:
-                Style.SpaceChar = new () { Rune = Glyphs.HLine }; // '─'
+                Style.SpaceChar = new () { Grapheme = Glyphs.HLine.ToString () }; // '─'
 
                 break;
             case Orientation.Vertical:
-                Style.SpaceChar = new () { Rune = Glyphs.VLine };
+                Style.SpaceChar = new () { Grapheme = Glyphs.VLine.ToString () };
 
                 break;
         }
@@ -799,7 +799,7 @@ public class Slider<T> : View, IOrientation
 
         if (_dragPosition.HasValue && _moveRenderPosition.HasValue)
         {
-            AddRune (_moveRenderPosition.Value.X, _moveRenderPosition.Value.Y, Style.DragChar.Rune);
+            AddStr (_moveRenderPosition.Value.X, _moveRenderPosition.Value.Y, Style.DragChar.Grapheme);
         }
 
         return true;
@@ -875,11 +875,11 @@ public class Slider<T> : View, IOrientation
                                       ? Style.RangeChar.Attribute ?? normalAttr
                                       : Style.SpaceChar.Attribute ?? normalAttr
                                  );
-            Rune rune = isSet && _config._type == SliderType.LeftRange ? Style.RangeChar.Rune : Style.SpaceChar.Rune;
+            string text = isSet && _config._type == SliderType.LeftRange ? Style.RangeChar.Grapheme : Style.SpaceChar.Grapheme;
 
             for (var i = 0; i < _config._startSpacing; i++)
             {
-                MoveAndAdd (x, y, rune);
+                MoveAndAdd (x, y, text);
 
                 if (isVertical)
                 {
@@ -897,7 +897,7 @@ public class Slider<T> : View, IOrientation
 
             for (var i = 0; i < _config._startSpacing; i++)
             {
-                MoveAndAdd (x, y, Style.EmptyChar.Rune);
+                MoveAndAdd (x, y, Style.EmptyChar.Grapheme);
 
                 if (isVertical)
                 {
@@ -951,25 +951,25 @@ public class Slider<T> : View, IOrientation
                                       drawRange ? Style.RangeChar.Attribute ?? setAttr : Style.OptionChar.Attribute ?? normalAttr
                                      );
 
-                Rune rune = drawRange ? Style.RangeChar.Rune : Style.OptionChar.Rune;
+                string text = drawRange ? Style.RangeChar.Grapheme : Style.OptionChar.Grapheme;
 
                 if (isSet)
                 {
                     if (_setOptions [0] == i)
                     {
-                        rune = Style.StartRangeChar.Rune;
+                        text = Style.StartRangeChar.Grapheme;
                     }
                     else if (_setOptions.Count > 1 && _setOptions [1] == i)
                     {
-                        rune = Style.EndRangeChar.Rune;
+                        text = Style.EndRangeChar.Grapheme;
                     }
                     else if (_setOptions.Contains (i))
                     {
-                        rune = Style.SetChar.Rune;
+                        text = Style.SetChar.Grapheme;
                     }
                 }
 
-                MoveAndAdd (x, y, rune);
+                MoveAndAdd (x, y, text);
 
                 if (isVertical)
                 {
@@ -992,7 +992,7 @@ public class Slider<T> : View, IOrientation
 
                     for (var s = 0; s < _config._cachedInnerSpacing; s++)
                     {
-                        MoveAndAdd (x, y, drawRange && isSet ? Style.RangeChar.Rune : Style.SpaceChar.Rune);
+                        MoveAndAdd (x, y, drawRange && isSet ? Style.RangeChar.Grapheme : Style.SpaceChar.Grapheme);
 
                         if (isVertical)
                         {
@@ -1017,11 +1017,11 @@ public class Slider<T> : View, IOrientation
                                       ? Style.RangeChar.Attribute ?? normalAttr
                                       : Style.SpaceChar.Attribute ?? normalAttr
                                  );
-            Rune rune = isSet && _config._type == SliderType.RightRange ? Style.RangeChar.Rune : Style.SpaceChar.Rune;
+            string text = isSet && _config._type == SliderType.RightRange ? Style.RangeChar.Grapheme : Style.SpaceChar.Grapheme;
 
             for (var i = 0; i < remaining; i++)
             {
-                MoveAndAdd (x, y, rune);
+                MoveAndAdd (x, y, text);
 
                 if (isVertical)
                 {
@@ -1039,7 +1039,7 @@ public class Slider<T> : View, IOrientation
 
             for (var i = 0; i < remaining; i++)
             {
-                MoveAndAdd (x, y, Style.EmptyChar.Rune);
+                MoveAndAdd (x, y, Style.EmptyChar.Grapheme);
 
                 if (isVertical)
                 {

+ 4 - 4
Terminal.Gui/Views/TableView/TreeTableSource.cs

@@ -88,14 +88,14 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
     {
         Branch<T> branch = RowToBranch (row);
 
-        // Everything on line before the expansion run and branch text
-        Rune [] prefix = branch.GetLinePrefix ().ToArray ();
-        Rune expansion = branch.GetExpandableSymbol ();
+        // Everything on the line before the expansion run and branch text
+        string [] prefix = branch.GetLinePrefix ().ToArray ();
+        string expansion = branch.GetExpandableSymbol ();
         string lineBody = _tree.AspectGetter (branch.Model) ?? "";
 
         var sb = new StringBuilder ();
 
-        foreach (Rune p in prefix)
+        foreach (string p in prefix)
         {
             sb.Append (p);
         }

+ 17 - 17
Terminal.Gui/Views/TextInput/TextField.cs

@@ -17,7 +17,7 @@ public class TextField : View, IDesignable
     private int _selectedStart; // -1 represents there is no text selection.
     private string _selectedText;
     private int _start;
-    private List<Rune> _text;
+    private List<string> _text;
 
     /// <summary>
     ///     Initializes a new instance of the <see cref="TextField"/> class.
@@ -541,7 +541,7 @@ public class TextField : View, IDesignable
             ClearAllSelection ();
 
             // Note we use NewValue here; TextChanging subscribers may have changed it
-            _text = args.Result.EnumerateRunes ().ToList ();
+            _text = args.Result.ToStringList ();
 
             if (!Secret && !_historyText.IsFromHistory)
             {
@@ -629,7 +629,7 @@ public class TextField : View, IDesignable
         }
 
         Clipboard.Contents = SelectedText;
-        List<Rune> newText = DeleteSelectedText ();
+        List<string> newText = DeleteSelectedText ();
         Text = StringExtensions.ToString (newText);
         Adjust ();
     }
@@ -700,7 +700,7 @@ public class TextField : View, IDesignable
         }
         else
         {
-            List<Rune> newText = DeleteSelectedText ();
+            List<string> newText = DeleteSelectedText ();
             Text = StringExtensions.ToString (newText);
             Adjust ();
         }
@@ -734,7 +734,7 @@ public class TextField : View, IDesignable
         }
         else
         {
-            List<Rune> newText = DeleteSelectedText ();
+            List<string> newText = DeleteSelectedText ();
             Text = StringExtensions.ToString (newText);
             Adjust ();
         }
@@ -943,8 +943,8 @@ public class TextField : View, IDesignable
 
         for (int idx = p; idx < tcount; idx++)
         {
-            Rune rune = _text [idx];
-            int cols = rune.GetColumns ();
+            string text = _text [idx];
+            int cols = text.GetColumns ();
 
             if (!Enabled)
             {
@@ -980,7 +980,7 @@ public class TextField : View, IDesignable
 
             if (col + cols <= width)
             {
-                AddRune (Secret ? Glyphs.Dot : rune);
+                AddStr (Secret ? Glyphs.Dot.ToString () : text);
             }
 
             if (!TextModel.SetCol (ref col, width, cols))
@@ -1254,7 +1254,7 @@ public class TextField : View, IDesignable
 
     private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode); }
 
-    private List<Rune> DeleteSelectedText ()
+    private List<string> DeleteSelectedText ()
     {
         SetSelectedStartSelectedLength ();
         int selStart = SelectedStart > -1 ? _start : _cursorPosition;
@@ -1270,7 +1270,7 @@ public class TextField : View, IDesignable
         ClearAllSelection ();
         _cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
 
-        return newText.ToRuneList ();
+        return newText.ToStringList ();
     }
 
     private void GenerateSuggestions ()
@@ -1318,7 +1318,7 @@ public class TextField : View, IDesignable
                           new (_cursorPosition, 0)
                          );
 
-        List<Rune> newText = _text;
+        List<string> newText = _text;
 
         if (SelectedLength > 0)
         {
@@ -1339,7 +1339,7 @@ public class TextField : View, IDesignable
 
             if (_cursorPosition == newText.Count + 1)
             {
-                SetText (newText.Concat (kbstr).ToList ());
+                SetText (newText.Concat (kbstr.Select (r => r.ToString ())).ToList ());
             }
             else
             {
@@ -1350,7 +1350,7 @@ public class TextField : View, IDesignable
 
                 SetText (
                          newText.GetRange (0, _preTextChangedCursorPos)
-                                .Concat (kbstr)
+                                .Concat (kbstr.Select (r => r.ToString ()))
                                 .Concat (
                                          newText.GetRange (
                                                            _preTextChangedCursorPos,
@@ -1367,7 +1367,7 @@ public class TextField : View, IDesignable
         {
             SetText (
                      newText.GetRange (0, _preTextChangedCursorPos)
-                            .Concat (kbstr)
+                            .Concat (kbstr.Select (r => r.ToString ()))
                             .Concat (
                                      newText.GetRange (
                                                        Math.Min (_preTextChangedCursorPos + 1, newText.Count),
@@ -1729,7 +1729,7 @@ public class TextField : View, IDesignable
         TitleTextFormatter.Draw (driver: Driver, screen: ViewportToScreen (new Rectangle (0, 0, Viewport.Width, 1)), normalColor: captionAttribute, hotColor: hotKeyAttribute);
     }
 
-    private void SetClipboard (IEnumerable<Rune> text)
+    private void SetClipboard (IEnumerable<string> text)
     {
         if (!Secret)
         {
@@ -1755,8 +1755,8 @@ public class TextField : View, IDesignable
         }
     }
 
-    private void SetText (List<Rune> newText) { Text = StringExtensions.ToString (newText); }
-    private void SetText (IEnumerable<Rune> newText) { SetText (newText.ToList ()); }
+    private void SetText (List<string> newText) { Text = StringExtensions.ToString (newText); }
+    private void SetText (IEnumerable<string> newText) { SetText (newText.ToList ()); }
 
     private void ShowContextMenu (bool keyboard)
     {

+ 58 - 52
Terminal.Gui/Views/TextInput/TextModel.cs

@@ -71,7 +71,7 @@ internal class TextModel
         for (int i = first; i < last; i++)
         {
             List<Cell> line = GetLine (i);
-            int tabSum = line.Sum (c => c.Rune.Value == '\t' ? Math.Max (tabWidth - 1, 0) : 0);
+            int tabSum = line.Sum (c => c.Grapheme == "\t" ? Math.Max (tabWidth - 1, 0) : 0);
             int l = line.Count + tabSum;
 
             if (l > maxLength)
@@ -222,7 +222,7 @@ internal class TextModel
 
             if (cell is { })
             {
-                rune = cell.Value.Rune;
+                rune = Rune.GetRuneAt (cell.Value.Grapheme, 0);
             }
             else
             {
@@ -299,10 +299,11 @@ internal class TextModel
                     }
 
                     List<Cell> line = GetLine (nRow);
+                    Rune firstRune = Rune.GetRuneAt (line [0].Grapheme, 0);
 
                     if (nCol == 0
                         && nRow == fromRow
-                        && (Rune.IsLetterOrDigit (line [0].Rune) || Rune.IsPunctuation (line [0].Rune) || Rune.IsSymbol (line [0].Rune)))
+                        && (Rune.IsLetterOrDigit (firstRune) || Rune.IsPunctuation (firstRune) || Rune.IsSymbol (firstRune)))
                     {
                         return;
                     }
@@ -366,7 +367,7 @@ internal class TextModel
 
         try
         {
-            Rune rune = _lines [row].Count > 0 ? RuneAt (col, row)!.Value.Rune : default (Rune);
+            Rune rune = _lines [row].Count > 0 ? Rune.GetRuneAt (RuneAt (col, row)!.Value.Grapheme, 0) : default (Rune);
             RuneType runeType = GetRuneType (rune);
 
             int lastValidCol = IsSameRuneType (rune, runeType, useSameRuneType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
@@ -425,10 +426,11 @@ internal class TextModel
                     }
 
                     List<Cell> line = GetLine (nRow);
+                    Rune firstRune = Rune.GetRuneAt (line [0].Grapheme, 0);
 
                     if (nCol == line.Count
                         && nRow == fromRow
-                        && (Rune.IsLetterOrDigit (line [0].Rune) || Rune.IsPunctuation (line [0].Rune) || Rune.IsSymbol (line [0].Rune)))
+                        && (Rune.IsLetterOrDigit (firstRune) || Rune.IsPunctuation (firstRune) || Rune.IsSymbol (firstRune)))
                     {
                         return;
                     }
@@ -475,10 +477,10 @@ internal class TextModel
         }
 
         if (startCol > 0
-            && StringExtensions.ToString (line.GetRange (startCol, col - startCol).Select (c => c.Rune).ToList ()).Trim () == ""
-            && (col - startCol > 1 || (col - startCol > 0 && line [startCol - 1].Rune == (Rune)' ')))
+            && StringExtensions.ToString (line.GetRange (startCol, col - startCol).Select (c => c.Grapheme).ToList ()).Trim () == ""
+            && (col - startCol > 1 || (col - startCol > 0 && line [startCol - 1].Grapheme == " ")))
         {
-            while (startCol > 0 && line [startCol - 1].Rune == (Rune)' ')
+            while (startCol > 0 && line [startCol - 1].Grapheme == " ")
             {
                 startCol--;
             }
@@ -495,13 +497,13 @@ internal class TextModel
 
         if (selectWordOnly)
         {
-            List<Rune> selRunes = line.GetRange (startCol, col - startCol).Select (c => c.Rune).ToList ();
+            List<string> selText = line.GetRange (startCol, col - startCol).Select (c => c.Grapheme).ToList ();
 
-            if (StringExtensions.ToString (selRunes).Trim () != "")
+            if (StringExtensions.ToString (selText).Trim () != "")
             {
-                for (int i = selRunes.Count - 1; i > -1; i--)
+                for (int i = selText.Count - 1; i > -1; i--)
                 {
-                    if (selRunes [i] == (Rune)' ')
+                    if (selText [i] == " ")
                     {
                         col--;
                     }
@@ -519,18 +521,18 @@ internal class TextModel
 
     internal static int CalculateLeftColumn (List<Cell> t, int start, int end, int width, int tabWidth = 0)
     {
-        List<Rune> runes = new ();
+        List<string> strings = new ();
 
         foreach (Cell cell in t)
         {
-            runes.Add (cell.Rune);
+            strings.Add (cell.Grapheme);
         }
 
-        return CalculateLeftColumn (runes, start, end, width, tabWidth);
+        return CalculateLeftColumn (strings, start, end, width, tabWidth);
     }
 
     // Returns the left column in a range of the string.
-    internal static int CalculateLeftColumn (List<Rune> t, int start, int end, int width, int tabWidth = 0)
+    internal static int CalculateLeftColumn (List<string> t, int start, int end, int width, int tabWidth = 0)
     {
         if (t is null || t.Count == 0)
         {
@@ -538,15 +540,15 @@ internal class TextModel
         }
 
         var size = 0;
-        int tcount = end > t.Count - 1 ? t.Count - 1 : end;
+        int tCount = end > t.Count - 1 ? t.Count - 1 : end;
         var col = 0;
 
-        for (int i = tcount; i >= 0; i--)
+        for (int i = tCount; i >= 0; i--)
         {
-            Rune rune = t [i];
-            size += rune.GetColumns ();
+            string text = t [i];
+            size += text.GetColumns (false);
 
-            if (rune.Value == '\t')
+            if (text == "\t")
             {
                 size += tabWidth + 1;
             }
@@ -576,23 +578,23 @@ internal class TextModel
         List<Cell> t,
         int start = -1,
         int end = -1,
-        bool checkNextRune = true,
+        bool checkNextText = true,
         int tabWidth = 0
     )
     {
-        List<Rune> runes = new ();
+        List<string> strings = new ();
 
         foreach (Cell cell in t)
         {
-            runes.Add (cell.Rune);
+            strings.Add (cell.Grapheme);
         }
 
-        return DisplaySize (runes, start, end, checkNextRune, tabWidth);
+        return DisplaySize (strings, start, end, checkNextText, tabWidth);
     }
 
     // Returns the size and length in a range of the string.
     internal static (int size, int length) DisplaySize (
-        List<Rune> t,
+        List<string> t,
         int start = -1,
         int end = -1,
         bool checkNextRune = true,
@@ -607,35 +609,35 @@ internal class TextModel
         var size = 0;
         var len = 0;
 
-        int tcount = end == -1 ? t.Count :
+        int tCount = end == -1 ? t.Count :
                      end > t.Count ? t.Count : end;
         int i = start == -1 ? 0 : start;
 
-        for (; i < tcount; i++)
+        for (; i < tCount; i++)
         {
-            Rune rune = t [i];
-            size += rune.GetColumns ();
-            len += rune.GetEncodingLength (Encoding.Unicode);
+            string text = t [i];
+            size += text.GetColumns (false);
+            len += text.Length;
 
-            if (rune.Value == '\t')
+            if (text == "\t")
             {
                 size += tabWidth + 1;
                 len += tabWidth - 1;
             }
 
-            if (checkNextRune && i == tcount - 1 && t.Count > tcount && IsWideRune (t [i + 1], tabWidth, out int s, out int l))
+            if (checkNextRune && i == tCount - 1 && t.Count > tCount && IsWideText (t [i + 1], tabWidth, out int s, out int l))
             {
                 size += s;
                 len += l;
             }
         }
 
-        bool IsWideRune (Rune r, int tWidth, out int s, out int l)
+        bool IsWideText (string s1, int tWidth, out int s, out int l)
         {
-            s = r.GetColumns ();
-            l = r.GetEncodingLength ();
+            s = s1.GetColumns ();
+            l = Encoding.Unicode.GetByteCount (s1);
 
-            if (r.Value == '\t')
+            if (s1 == "\t")
             {
                 s += tWidth + 1;
                 l += tWidth - 1;
@@ -744,17 +746,17 @@ internal class TextModel
 
     internal static int GetColFromX (List<Cell> t, int start, int x, int tabWidth = 0)
     {
-        List<Rune> runes = new ();
+        List<string> strings = new ();
 
         foreach (Cell cell in t)
         {
-            runes.Add (cell.Rune);
+            strings.Add (cell.Grapheme);
         }
 
-        return GetColFromX (runes, start, x, tabWidth);
+        return GetColFromX (strings, start, x, tabWidth);
     }
 
-    internal static int GetColFromX (List<Rune> t, int start, int x, int tabWidth = 0)
+    internal static int GetColFromX (List<string> t, int start, int x, int tabWidth = 0)
     {
         if (x < 0)
         {
@@ -766,10 +768,10 @@ internal class TextModel
 
         for (int i = start; i < t.Count; i++)
         {
-            Rune r = t [i];
-            size += r.GetColumns ();
+            string s = t [i];
+            size += s.GetColumns ();
 
-            if (r.Value == '\t')
+            if (s == "\t")
             {
                 size += tabWidth + 1;
             }
@@ -1055,18 +1057,21 @@ internal class TextModel
         if (col + 1 < line.Count)
         {
             col++;
-            rune = line [col].Rune;
+            rune = Rune.GetRuneAt (line [col].Grapheme, 0);
+            Rune prevRune = Rune.GetRuneAt (line [col - 1].Grapheme, 0);
 
             if (col + 1 == line.Count
                 && !Rune.IsLetterOrDigit (rune)
-                && !Rune.IsWhiteSpace (line [col - 1].Rune)
-                && IsSameRuneType (line [col - 1].Rune, GetRuneType (rune), useSameRuneType))
+                && !Rune.IsWhiteSpace (prevRune)
+                                       && IsSameRuneType (prevRune, GetRuneType (rune), useSameRuneType))
             {
                 col++;
             }
 
+            prevRune = Rune.GetRuneAt (line [col - 1].Grapheme, 0);
+
             if (!Rune.IsWhiteSpace (rune)
-                && (Rune.IsWhiteSpace (line [col - 1].Rune) || !IsSameRuneType (line [col - 1].Rune, GetRuneType (rune), useSameRuneType)))
+                && (Rune.IsWhiteSpace (prevRune) || !IsSameRuneType (prevRune, GetRuneType (rune), useSameRuneType)))
             {
                 return false;
             }
@@ -1097,12 +1102,13 @@ internal class TextModel
         if (col > 0)
         {
             col--;
-            rune = line [col].Rune;
+            rune = Rune.GetRuneAt (line [col].Grapheme, 0);
+            Rune nextRune = Rune.GetRuneAt (line [col + 1].Grapheme, 0);
 
             if ((!Rune.IsWhiteSpace (rune)
-                 && !Rune.IsWhiteSpace (line [col + 1].Rune)
-                 && !IsSameRuneType (line [col + 1].Rune, GetRuneType (rune), useSameRuneType))
-                || (Rune.IsWhiteSpace (rune) && !Rune.IsWhiteSpace (line [col + 1].Rune)))
+                 && !Rune.IsWhiteSpace (nextRune)
+                 && !IsSameRuneType (nextRune, GetRuneType (rune), useSameRuneType))
+                || (Rune.IsWhiteSpace (rune) && !Rune.IsWhiteSpace (nextRune)))
             {
                 return false;
             }

+ 27 - 28
Terminal.Gui/Views/TextInput/TextView.cs

@@ -1481,7 +1481,7 @@ public class TextView : View, IDesignable
     }
 
     /// <summary>Loads the contents of the <see cref="Cell"/> list into the <see cref="TextView"/>.</summary>
-    /// <param name="cells">Rune cells list to load the contents from.</param>
+    /// <param name="cells">Text cells list to load the contents from.</param>
     public void Load (List<Cell> cells)
     {
         SetWrapModel ();
@@ -1801,8 +1801,8 @@ public class TextView : View, IDesignable
 
             for (int idxCol = _leftColumn; idxCol < lineRuneCount; idxCol++)
             {
-                Rune rune = idxCol >= lineRuneCount ? (Rune)' ' : line [idxCol].Rune;
-                int cols = rune.GetColumns ();
+                string text = idxCol >= lineRuneCount ? " " : line [idxCol].Grapheme;
+                int cols = text.GetColumns (false);
 
                 if (idxCol < line.Count && IsSelecting && PointInSelection (idxCol, idxRow))
                 {
@@ -1821,7 +1821,7 @@ public class TextView : View, IDesignable
                     OnDrawNormalColor (line, idxCol, idxRow);
                 }
 
-                if (rune.Value == '\t')
+                if (text == "\t")
                 {
                     cols += TabWidth + 1;
 
@@ -1840,7 +1840,7 @@ public class TextView : View, IDesignable
                 }
                 else
                 {
-                    AddRune (col, row, rune);
+                    AddStr (col, row, text);
 
                     // Ensures that cols less than 0 to be 1 because it will be converted to a printable rune
                     cols = Math.Max (cols, 1);
@@ -1851,7 +1851,7 @@ public class TextView : View, IDesignable
                     break;
                 }
 
-                if (idxCol + 1 < lineRuneCount && col + line [idxCol + 1].Rune.GetColumns () > right)
+                if (idxCol + 1 < lineRuneCount && col + line [idxCol + 1].Grapheme.GetColumns () > right)
                 {
                     break;
                 }
@@ -2047,9 +2047,9 @@ public class TextView : View, IDesignable
                     break;
                 }
 
-                int cols = line [idx].Rune.GetColumns ();
+                int cols = line [idx].Grapheme.GetColumns ();
 
-                if (line [idx].Rune.Value == '\t')
+                if (line [idx].Grapheme == "\t")
                 {
                     cols += TabWidth + 1;
                 }
@@ -2806,12 +2806,12 @@ public class TextView : View, IDesignable
             cells = line.GetRange (startCol, endCol - startCol);
             cellsList.Add (cells);
 
-            return StringFromRunes (cells);
+            return StringFromCells (cells);
         }
 
         cells = line.GetRange (startCol, line.Count - startCol);
         cellsList.Add (cells);
-        string res = StringFromRunes (cells);
+        string res = StringFromCells (cells);
 
         for (int row = startRow + 1; row < maxRow; row++)
         {
@@ -2821,14 +2821,14 @@ public class TextView : View, IDesignable
 
             res = res
                   + Environment.NewLine
-                  + StringFromRunes (cells);
+                  + StringFromCells (cells);
         }
 
         line = model is null ? _model.GetLine (maxRow) : model.GetLine (maxRow);
         cellsList.AddRange ([]);
         cells = line.GetRange (0, endCol);
         cellsList.Add (cells);
-        res = res + Environment.NewLine + StringFromRunes (cells);
+        res = res + Environment.NewLine + StringFromCells (cells);
 
         return res;
     }
@@ -3108,7 +3108,7 @@ public class TextView : View, IDesignable
         {
             if (Used)
             {
-                Insert (new () { Rune = a.AsRune, Attribute = attribute });
+                Insert (new () { Grapheme = a.AsRune.ToString (), Attribute = attribute });
                 CurrentColumn++;
 
                 if (CurrentColumn >= _leftColumn + Viewport.Width)
@@ -3119,7 +3119,7 @@ public class TextView : View, IDesignable
             }
             else
             {
-                Insert (new () { Rune = a.AsRune, Attribute = attribute });
+                Insert (new () { Grapheme = a.AsRune.ToString (), Attribute = attribute });
                 CurrentColumn++;
             }
         }
@@ -3207,7 +3207,7 @@ public class TextView : View, IDesignable
             int restCount = currentLine.Count - CurrentColumn;
             List<Cell> rest = currentLine.GetRange (CurrentColumn, restCount);
             var val = string.Empty;
-            val += StringFromRunes (rest);
+            val += StringFromCells (rest);
 
             if (_lastWasKill)
             {
@@ -3313,7 +3313,7 @@ public class TextView : View, IDesignable
             int restCount = CurrentColumn;
             List<Cell> rest = currentLine.GetRange (0, restCount);
             var val = string.Empty;
-            val += StringFromRunes (rest);
+            val += StringFromCells (rest);
 
             if (_lastWasKill)
             {
@@ -3842,7 +3842,7 @@ public class TextView : View, IDesignable
 
             List<Cell> currentLine = GetCurrentLine ();
 
-            if (currentLine.Count > 0 && currentLine [CurrentColumn - 1].Rune.Value == '\t')
+            if (currentLine.Count > 0 && currentLine[CurrentColumn - 1].Grapheme == "\t")
             {
                 _historyText.Add (new () { new (currentLine) }, CursorPosition);
 
@@ -4601,29 +4601,28 @@ public class TextView : View, IDesignable
         _isButtonShift = false;
     }
 
-    private string StringFromRunes (List<Cell> cells)
+    private string StringFromCells (List<Cell> cells)
     {
-        if (cells is null)
-        {
-            throw new ArgumentNullException (nameof (cells));
-        }
+        ArgumentNullException.ThrowIfNull (cells);
 
         var size = 0;
-
         foreach (Cell cell in cells)
         {
-            size += cell.Rune.GetEncodingLength ();
+            string t = cell.Grapheme;
+            size += Encoding.Unicode.GetByteCount (t);
         }
 
-        var encoded = new byte [size];
+        byte [] encoded = new byte [size];
         var offset = 0;
-
         foreach (Cell cell in cells)
         {
-            offset += cell.Rune.Encode (encoded, offset);
+            string t = cell.Grapheme;
+            int bytesWritten = Encoding.Unicode.GetBytes (t, 0, t.Length, encoded, offset);
+            offset += bytesWritten;
         }
 
-        return StringExtensions.ToString (encoded);
+        // decode using the same encoding and the bytes actually written
+        return Encoding.Unicode.GetString (encoded, 0, offset);
     }
 
     private void TextView_SuperViewChanged (object sender, SuperViewChangedEventArgs e)

+ 3 - 3
Terminal.Gui/Views/Toplevel.cs

@@ -13,7 +13,7 @@ namespace Terminal.Gui.Views;
 ///     </para>
 ///     <para>
 ///         A Toplevel is created when an application initializes Terminal.Gui by calling <see cref="IApplication.Init"/>.
-///         The application Toplevel can be accessed via <see cref="IApplication.Current"/>. Additional Toplevels can be created
+///         The application Toplevel can be accessed via <see cref="IApplication.TopRunnable"/>. Additional Toplevels can be created
 ///         and run (e.g. <see cref="Dialog"/>s). To run a Toplevel, create the <see cref="Toplevel"/> and call
 ///         <see cref="IApplication.Run(Toplevel, Func{Exception, bool})"/>.
 ///     </para>
@@ -152,7 +152,7 @@ public partial class Toplevel : View
     /// </summary>
     public virtual void RequestStop ()
     {
-        App?.RequestStop (App?.Current);
+        App?.RequestStop (App?.TopRunnable);
     }
 
     /// <summary>
@@ -244,7 +244,7 @@ public partial class Toplevel : View
         }
 
         // BUGBUG: The && true is a temp hack
-        if ((superView != top || top?.SuperView is { } || (top != App?.Current && top!.Modal) || (top == App?.Current && top?.SuperView is null))
+        if ((superView != top || top?.SuperView is { } || (top != App?.TopRunnable && top!.Modal) || (top == App?.TopRunnable && top?.SuperView is null))
             && (top!.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y))
 
         {

+ 21 - 21
Terminal.Gui/Views/TreeView/Branch.cs

@@ -87,9 +87,9 @@ internal class Branch<T> where T : class
             isSelected ? _tree.HasFocus ? _tree.GetAttributeForRole (VisualRole.Focus) : _tree.GetAttributeForRole (VisualRole.HotNormal) : _tree.GetAttributeForRole (VisualRole.Normal);
         Attribute symbolColor = _tree.Style.HighlightModelTextOnly ? _tree.GetAttributeForRole (VisualRole.Normal) : textColor;
 
-        // Everything on line before the expansion run and branch text
-        Rune [] prefix = GetLinePrefix ().ToArray ();
-        Rune expansion = GetExpandableSymbol ();
+        // Everything on the line before the expansion run and branch text
+        string [] prefix = GetLinePrefix ().ToArray ();
+        string expansion = GetExpandableSymbol ();
         string lineBody = _tree.AspectGetter (Model) ?? "";
 
         _tree.Move (0, y);
@@ -99,7 +99,7 @@ internal class Branch<T> where T : class
         Attribute attr = symbolColor;
 
         // Draw the line prefix (all parallel lanes or whitespace and an expand/collapse/leaf symbol)
-        foreach (Rune r in prefix)
+        foreach (string s in prefix)
         {
             if (toSkip > 0)
             {
@@ -107,8 +107,8 @@ internal class Branch<T> where T : class
             }
             else
             {
-                cells.Add (NewCell (attr, r));
-                availableWidth -= r.GetColumns ();
+                cells.Add (NewCell (attr, s));
+                availableWidth -= s.GetColumns ();
             }
         }
 
@@ -212,7 +212,7 @@ internal class Branch<T> where T : class
         }
 
         attr = modelColor;
-        cells.AddRange (lineBody.Select (r => NewCell (attr, new (r))));
+        cells.AddRange (lineBody.Select (c => NewCell (attr, c.ToString ())));
 
         if (availableWidth > 0)
         {
@@ -220,7 +220,7 @@ internal class Branch<T> where T : class
 
             cells.AddRange (
                             Enumerable.Repeat (
-                                               NewCell (attr, new (' ')),
+                                               NewCell (attr, " "),
                                                availableWidth
                                               )
                            );
@@ -243,7 +243,7 @@ internal class Branch<T> where T : class
             foreach (Cell cell in cells)
             {
                 _tree.SetAttribute ((Attribute)cell.Attribute!);
-                _tree.AddRune (cell.Rune);
+                _tree.AddStr (cell.Grapheme);
             }
         }
 
@@ -288,21 +288,21 @@ internal class Branch<T> where T : class
     ///     object to indicate whether it <see cref="IsExpanded"/> or not (or it is a leaf).
     /// </summary>
     /// <returns></returns>
-    public Rune GetExpandableSymbol ()
+    public string GetExpandableSymbol ()
     {
         Rune leafSymbol = _tree.Style.ShowBranchLines ? Glyphs.HLine : (Rune)' ';
 
         if (IsExpanded)
         {
-            return _tree.Style.CollapseableSymbol ?? leafSymbol;
+            return _tree.Style.CollapseableSymbol.ToString () ?? leafSymbol.ToString ();
         }
 
         if (CanExpand ())
         {
-            return _tree.Style.ExpandableSymbol ?? leafSymbol;
+            return _tree.Style.ExpandableSymbol.ToString () ?? leafSymbol.ToString ();
         }
 
-        return leafSymbol;
+        return leafSymbol.ToString ();
     }
 
     /// <summary>
@@ -409,14 +409,14 @@ internal class Branch<T> where T : class
     ///     any tree branches (if enabled).
     /// </summary>
     /// <returns></returns>
-    internal IEnumerable<Rune> GetLinePrefix ()
+    internal IEnumerable<string> GetLinePrefix ()
     {
         // If not showing line branches or this is a root object.
         if (!_tree.Style.ShowBranchLines)
         {
             for (var i = 0; i < Depth; i++)
             {
-                yield return new (' ');
+                yield return new (" ");
             }
 
             yield break;
@@ -427,23 +427,23 @@ internal class Branch<T> where T : class
         {
             if (cur.IsLast ())
             {
-                yield return new (' ');
+                yield return new (" ");
             }
             else
             {
-                yield return Glyphs.VLine;
+                yield return Glyphs.VLine.ToString ();
             }
 
-            yield return new (' ');
+            yield return new (" ");
         }
 
         if (IsLast ())
         {
-            yield return Glyphs.LLCorner;
+            yield return Glyphs.LLCorner.ToString ();
         }
         else
         {
-            yield return Glyphs.LeftTee;
+            yield return Glyphs.LeftTee.ToString ();
         }
     }
 
@@ -531,5 +531,5 @@ internal class Branch<T> where T : class
         return Parent.ChildBranches.LastOrDefault () == this;
     }
 
-    private static Cell NewCell (Attribute attr, Rune r) { return new () { Rune = r, Attribute = new (attr) }; }
+    private static Cell NewCell (Attribute attr, string s) { return new () { Grapheme = s, Attribute = new (attr) }; }
 }

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

@@ -42,7 +42,7 @@ namespace Terminal.Gui.Views;
 ///     Application.RequestStop();
 /// };
 /// 
-/// Application.Current.Add (wizard);
+/// Application.TopRunnable.Add (wizard);
 /// Application.Run ();
 /// Application.Shutdown ();
 /// </code>

+ 4 - 4
Tests/IntegrationTests/FluentTests/GuiTestContextKeyEventTests.cs

@@ -16,9 +16,9 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper)
     public void QuitKey_ViaApplication_Stops (TestDriver d)
     {
         using GuiTestContext context = With.A<Window> (40, 10, d);
-        Assert.True (context.App?.Current!.Running);
+        Assert.True (context.App?.TopRunnable!.Running);
 
-        Toplevel? top = context.App?.Current;
+        Toplevel? top = context.App?.TopRunnable;
         context.Then ((_) => context!.App?.Keyboard.RaiseKeyDownEvent (Application.QuitKey));
         Assert.False (top!.Running);
     }
@@ -28,9 +28,9 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper)
     public void QuitKey_ViaEnqueueKey_Stops (TestDriver d)
     {
         using GuiTestContext context = With.A<Window> (40, 10, d, _out);
-        Assert.True (context.App?.Current!.Running);
+        Assert.True (context.App?.TopRunnable!.Running);
 
-        Toplevel? top = context.App?.Current;
+        Toplevel? top = context.App?.TopRunnable;
         context.EnqueueKeyEvent (Application.QuitKey);
 
         Assert.False (top!.Running);

+ 4 - 4
Tests/IntegrationTests/FluentTests/GuiTestContextTests.cs

@@ -43,7 +43,7 @@ public class GuiTestContextTests (ITestOutputHelper outputHelper)
     public void With_New_A_Runs (TestDriver d)
     {
         using GuiTestContext context = With.A<Window> (40, 10, d, _out);
-        Assert.True (context.App!.Current!.Running);
+        Assert.True (context.App!.TopRunnable!.Running);
         Assert.NotEqual (Rectangle.Empty, context.App!.Screen);
     }
 
@@ -54,9 +54,9 @@ public class GuiTestContextTests (ITestOutputHelper outputHelper)
         using GuiTestContext context = With.A<Window> (10, 3, d, _out)
                                            .Then ((app) =>
                                                   {
-                                                      app.Current!.BorderStyle = LineStyle.None;
-                                                      app.Current!.Border!.Thickness = Thickness.Empty;
-                                                      app.Current.Text = "hello";
+                                                      app.TopRunnable!.BorderStyle = LineStyle.None;
+                                                      app.TopRunnable!.Border!.Thickness = Thickness.Empty;
+                                                      app.TopRunnable.Text = "hello";
                                                   })
                                            .ScreenShot ("ScreenShot", _out)
                                            .AnsiScreenShot ("AnsiScreenShot", _out)

+ 23 - 23
Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs

@@ -143,7 +143,7 @@ public class MenuBarv2Tests
                                      .Then ((app) =>
                                             {
                                                 menuBar = new MenuBarv2 ();
-                                                top = app.Current!;
+                                                top = app.TopRunnable!;
 
                                                 top.Add (
                                                          new View ()
@@ -153,7 +153,7 @@ public class MenuBarv2Tests
 
                                                          });
                                                 menuBar.EnableForDesign (ref top);
-                                                app.Current!.Add (menuBar);
+                                                app.TopRunnable!.Add (menuBar);
                                             })
                                      .WaitIteration ()
                                      .AssertIsNotType<MenuItemv2> (top?.App?.Navigation!.GetFocused ())
@@ -178,7 +178,7 @@ public class MenuBarv2Tests
                                             {
                                                 app = a;
                                                 menuBar = new MenuBarv2 ();
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
 
                                                 top.Add (
                                                          new View ()
@@ -188,7 +188,7 @@ public class MenuBarv2Tests
 
                                                          });
                                                 menuBar.EnableForDesign (ref top);
-                                                app.Current!.Add (menuBar);
+                                                app.TopRunnable!.Add (menuBar);
                                             })
                                      .WaitIteration ()
                                      .AssertIsNotType<MenuItemv2> (app?.Navigation!.GetFocused ())
@@ -266,10 +266,10 @@ public class MenuBarv2Tests
                                      .Then ((app) =>
                                             {
                                                 var menuBar = new MenuBarv2 ();
-                                                app.Current!.Add (menuBar);
+                                                app.TopRunnable!.Add (menuBar);
 
                                                 // Call EnableForDesign
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
                                                 bool result = menuBar.EnableForDesign (ref top);
 
                                                 // Should return true
@@ -302,9 +302,9 @@ public class MenuBarv2Tests
                                             {
                                                 app = a;
                                                 menuBar = new MenuBarv2 ();
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
                                                 menuBar.EnableForDesign (ref top);
-                                                app.Current!.Add (menuBar);
+                                                app.TopRunnable!.Add (menuBar);
                                             })
                                      .WaitIteration ()
                                      .ScreenShot ("MenuBar initial state", _out)
@@ -342,7 +342,7 @@ public class MenuBarv2Tests
                                             {
                                                 app = a;
                                                 menuBar = new MenuBarv2 ();
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
 
                                                 top.Add (
                                                          new View ()
@@ -352,7 +352,7 @@ public class MenuBarv2Tests
 
                                                          });
                                                 menuBar.EnableForDesign (ref top);
-                                                app.Current!.Add (menuBar);
+                                                app.TopRunnable!.Add (menuBar);
                                             })
                                      .AssertIsNotType<MenuItemv2> (app!.Navigation!.GetFocused ())
                                      .ScreenShot ("MenuBar initial state", _out)
@@ -386,9 +386,9 @@ public class MenuBarv2Tests
                                             {
                                                 app = a;
                                                 menuBar = new MenuBarv2 ();
-                                                Toplevel? toplevel = app.Current;
+                                                Toplevel? toplevel = app.TopRunnable;
                                                 menuBar.EnableForDesign (ref toplevel!);
-                                                app.Current!.Add (menuBar);
+                                                app.TopRunnable!.Add (menuBar);
                                             })
                                      .WaitIteration ()
                                      .AssertIsNotType<MenuItemv2> (app?.Navigation!.GetFocused ())
@@ -417,7 +417,7 @@ public class MenuBarv2Tests
                                             {
                                                 app = a;
                                                 menuBar = new MenuBarv2 ();
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
 
                                                 top.Add (
                                                          new View ()
@@ -427,18 +427,18 @@ public class MenuBarv2Tests
 
                                                          });
                                                 menuBar.EnableForDesign (ref top);
-                                                app.Current!.Add (menuBar);
+                                                app.TopRunnable!.Add (menuBar);
                                             })
                                      .WaitIteration ()
                                      .AssertIsNotType<MenuItemv2> (app!.Navigation!.GetFocused ())
                                      .ScreenShot ("MenuBar initial state", _out)
                                      .EnqueueKeyEvent (MenuBarv2.DefaultKey)
                                      .AssertEqual ("_New file", app.Navigation!.GetFocused ()!.Title)
-                                     .AssertTrue (app?.Current!.Running)
+                                     .AssertTrue (app?.TopRunnable!.Running)
                                      .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out)
                                      .EnqueueKeyEvent (Application.QuitKey)
                                      .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu)
-                                     .AssertTrue (app!.Current!.Running);
+                                     .AssertTrue (app!.TopRunnable!.Running);
     }
 
     [Theory]
@@ -453,7 +453,7 @@ public class MenuBarv2Tests
                                             {
                                                 app = a;
                                                 menuBar = new MenuBarv2 ();
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
 
                                                 top.Add (
                                                          new View ()
@@ -470,7 +470,7 @@ public class MenuBarv2Tests
                                                     item.Key = Key.Empty;
                                                 }
 
-                                                app.Current!.Add (menuBar);
+                                                app.TopRunnable!.Add (menuBar);
                                             })
                                      .WaitIteration ()
                                      .AssertIsNotType<MenuItemv2> (app?.Navigation!.GetFocused ())
@@ -480,7 +480,7 @@ public class MenuBarv2Tests
                                      .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out)
                                      .EnqueueKeyEvent (Application.QuitKey)
                                      .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu)
-                                     .AssertTrue (app?.Current!.Running);
+                                     .AssertTrue (app?.TopRunnable!.Running);
     }
 
     [Theory]
@@ -506,9 +506,9 @@ public class MenuBarv2Tests
                                      .Then ((a) =>
                                             {
                                                 var menuBar = new MenuBarv2 ();
-                                                Toplevel top = a.Current!;
+                                                Toplevel top = a.TopRunnable!;
                                                 menuBar.EnableForDesign (ref top);
-                                                a.Current!.Add (menuBar);
+                                                a.TopRunnable!.Add (menuBar);
                                             })
                                      .Add (testView)
                                      .WaitIteration ()
@@ -540,9 +540,9 @@ public class MenuBarv2Tests
                                      .Then ((a) =>
                                             {
                                                 var menuBar = new MenuBarv2 ();
-                                                Toplevel top = a.Current!;
+                                                Toplevel top = a.TopRunnable!;
                                                 menuBar.EnableForDesign (ref top);
-                                                a.Current!.Add (menuBar);
+                                                a.TopRunnable!.Add (menuBar);
                                             })
                                      .Add (testView)
                                      .WaitIteration ()

+ 2 - 2
Tests/IntegrationTests/FluentTests/NavigationTests.cs

@@ -29,8 +29,8 @@ public class NavigationTests (ITestOutputHelper outputHelper)
                                                 w2.Add (v3, v4);
                                                 var w3 = new Window { Id = "w3" };
                                                 w3.Add (v5, v6);
-                                                Toplevel top = app?.Current!;
-                                                app?.Current!.Add (w1, w2, w3);
+                                                Toplevel top = app?.TopRunnable!;
+                                                app?.TopRunnable!.Add (w1, w2, w3);
                                             })
                                      .AssertTrue (v5.HasFocus)
                                      .EnqueueKeyEvent (Key.F6)

+ 16 - 16
Tests/IntegrationTests/FluentTests/PopverMenuTests.cs

@@ -26,10 +26,10 @@ public class PopoverMenuTests
                                      .Then ((app) =>
                                             {
                                                 PopoverMenu popoverMenu = new ();
-                                                app.Current!.Add (popoverMenu);
+                                                app.TopRunnable!.Add (popoverMenu);
 
                                                 // Call EnableForDesign
-                                                Toplevel top = app.Current;
+                                                Toplevel top = app.TopRunnable;
                                                 bool result = popoverMenu.EnableForDesign (ref top);
 
                                                 // Should return true
@@ -65,7 +65,7 @@ public class PopoverMenuTests
                                                     };
 
                                                     // Call EnableForDesign
-                                                    Toplevel top = app.Current!;
+                                                    Toplevel top = app.TopRunnable!;
                                                     popoverMenu.EnableForDesign (ref top);
 
                                                     var view = new View
@@ -76,7 +76,7 @@ public class PopoverMenuTests
                                                         Id = "focusableView",
                                                         Text = "View"
                                                     };
-                                                    app.Current!.Add (view);
+                                                    app.TopRunnable!.Add (view);
 
                                                     // EnableForDesign sets to true; undo that
                                                     popoverMenu.Visible = false;
@@ -110,7 +110,7 @@ public class PopoverMenuTests
                                                 };
 
                                                 // Call EnableForDesign
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
                                                 bool result = popoverMenu.EnableForDesign (ref top);
 
                                                 var view = new View
@@ -121,7 +121,7 @@ public class PopoverMenuTests
                                                     Id = "focusableView",
                                                     Text = "View"
                                                 };
-                                                app.Current!.Add (view);
+                                                app.TopRunnable!.Add (view);
 
                                                 // EnableForDesign sets to true; undo that
                                                 popoverMenu.Visible = false;
@@ -139,7 +139,7 @@ public class PopoverMenuTests
                                      .ScreenShot ($"After {Application.QuitKey}", _out)
                                      .AssertFalse (app?.Popover!.Popovers.Cast<PopoverMenu> ().FirstOrDefault ()!.Visible)
                                      .AssertNull (app?.Popover!.GetActivePopover ())
-                                     .AssertTrue (app?.Current!.Running);
+                                     .AssertTrue (app?.TopRunnable!.Running);
     }
 
     [Theory]
@@ -157,7 +157,7 @@ public class PopoverMenuTests
                                                 };
 
                                                 // Call EnableForDesign
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
                                                 bool result = popoverMenu.EnableForDesign (ref top);
 
                                                 var view = new View
@@ -168,7 +168,7 @@ public class PopoverMenuTests
                                                     Id = "focusableView",
                                                     Text = "View"
                                                 };
-                                                app.Current!.Add (view);
+                                                app.TopRunnable!.Add (view);
 
                                                 // EnableForDesign sets to true; undo that
                                                 popoverMenu.Visible = false;
@@ -206,7 +206,7 @@ public class PopoverMenuTests
                                                 };
 
                                                 // Call EnableForDesign
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
                                                 bool result = popoverMenu.EnableForDesign (ref top);
 
                                                 var view = new View
@@ -217,7 +217,7 @@ public class PopoverMenuTests
                                                     Id = "focusableView",
                                                     Text = "View"
                                                 };
-                                                app.Current!.Add (view);
+                                                app.TopRunnable!.Add (view);
 
                                                 // EnableForDesign sets to true; undo that
                                                 popoverMenu.Visible = false;
@@ -231,11 +231,11 @@ public class PopoverMenuTests
                                      .Then ((_) => app?.Popover!.Show (app?.Popover.Popovers.First ()))
                                      .ScreenShot ("PopoverMenu after Show", _out)
                                      .AssertEqual ("Cu_t", app?.Navigation!.GetFocused ()!.Title)
-                                     .AssertTrue (app?.Current!.Running)
+                                     .AssertTrue (app?.TopRunnable!.Running)
                                      .EnqueueKeyEvent (Application.QuitKey)
                                      .ScreenShot ($"After {Application.QuitKey}", _out)
                                      .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu)
-                                     .AssertTrue (app?.Current!.Running);
+                                     .AssertTrue (app?.TopRunnable!.Running);
     }
 
     [Theory]
@@ -267,7 +267,7 @@ public class PopoverMenuTests
                                                 {
                                                     App = app
                                                 };
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
                                                 popoverMenu.EnableForDesign (ref top);
                                                 app?.Popover!.Register (popoverMenu);
                                             })
@@ -307,7 +307,7 @@ public class PopoverMenuTests
                                                 {
                                                     App = app
                                                 };
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
                                                 popoverMenu.EnableForDesign (ref top);
                                                 app?.Popover!.Register (popoverMenu);
                                             })
@@ -346,7 +346,7 @@ public class PopoverMenuTests
                                                 {
                                                     App = app
                                                 };
-                                                Toplevel top = app.Current!;
+                                                Toplevel top = app.TopRunnable!;
                                                 popoverMenu.EnableForDesign (ref top);
                                                 app?.Popover!.Register (popoverMenu);
                                             })

+ 3 - 1
Tests/IntegrationTests/UICatalog/ScenarioTests.cs

@@ -35,7 +35,9 @@ public class ScenarioTests : TestsAllViews
             return;
         }
 
-        Application.ResetState (true);
+        // Force a complete reset
+        ApplicationImpl.SetInstance (null);
+        CM.Disable (true);
 
         _output.WriteLine ($"Running Scenario '{scenarioType}'");
         Scenario? scenario = null;

+ 2 - 2
Tests/StressTests/ApplicationStressTests.cs

@@ -71,8 +71,8 @@ public class ApplicationStressTests
                 {
                     int tbNow = _tbCounter;
 
-                    // Wait for Application.Current to be running to ensure timed events can be processed
-                    while (Application.Current is null || Application.Current is { Running: false })
+                    // Wait for Application.TopRunnable to be running to ensure timed events can be processed
+                    while (Application.TopRunnable is null || Application.TopRunnable is { Running: false })
                     {
                         Thread.Sleep (1);
                     }

+ 2 - 2
Tests/StressTests/ScenariosStressTests.cs

@@ -126,7 +126,7 @@ public class ScenariosStressTests
 
         void OnApplicationSessionBegun (object? sender, SessionTokenEventArgs e)
         {
-            // Get a list of all subviews under Application.Current (and their subviews, etc.)
+            // Get a list of all subviews under Application.TopRunnable (and their subviews, etc.)
             // and subscribe to their DrawComplete event
             void SubscribeAllSubViews (View view)
             {
@@ -140,7 +140,7 @@ public class ScenariosStressTests
                 }
             }
 
-            SubscribeAllSubViews (Application.Current!);
+            SubscribeAllSubViews (Application.TopRunnable!);
         }
 
         // If the scenario doesn't close within the abort time, this will force it to quit

+ 1 - 1
Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationLifecycle.cs

@@ -13,7 +13,7 @@ internal class FakeApplicationLifecycle (CancellationTokenSource hardStop) : IDi
     {
         hardStop.Cancel ();
 
-        Application.Current?.Dispose ();
+        Application.TopRunnable?.Dispose ();
         Application.Shutdown ();
     }
 }

+ 2 - 2
Tests/TerminalGuiFluentTesting/GuiTestContext.Navigation.cs

@@ -40,13 +40,13 @@ public partial class GuiTestContext
     public GuiTestContext Focus<T> (Func<T, bool>? evaluator = null) where T : View
     {
         evaluator ??= _ => true;
-        Toplevel? t = App?.Current;
+        Toplevel? t = App?.TopRunnable;
 
         HashSet<View> seen = new ();
 
         if (t == null)
         {
-            Fail ("Application.Current was null when trying to set focus");
+            Fail ("Application.TopRunnable was null when trying to set focus");
 
             return this;
         }

+ 4 - 4
Tests/TerminalGuiFluentTesting/GuiTestContext.ViewBase.cs

@@ -14,7 +14,7 @@ public partial class GuiTestContext
     {
         WaitIteration ((app) =>
                        {
-                           Toplevel top = app.Current ?? throw new ("Top was null so could not add view");
+                           Toplevel top = app.TopRunnable ?? throw new ("Top was null so could not add view");
                            top.Add (v);
                            top.Layout ();
                            _lastView = v;
@@ -28,15 +28,15 @@ public partial class GuiTestContext
     /// <summary>
     ///     The last view added (e.g. with <see cref="Add"/>) or the root/current top.
     /// </summary>
-    public View LastView => _lastView ?? App?.Current ?? throw new ("Could not determine which view to add to");
+    public View LastView => _lastView ?? App?.TopRunnable ?? throw new ("Could not determine which view to add to");
 
     private T Find<T> (Func<T, bool> evaluator) where T : View
     {
-        Toplevel? t = App?.Current;
+        Toplevel? t = App?.TopRunnable;
 
         if (t == null)
         {
-            Fail ("App.Current was null when attempting to find view");
+            Fail ("App.TopRunnable was null when attempting to find view");
         }
 
         T? f = FindRecursive (t!, evaluator);

+ 12 - 12
Tests/UnitTests/Application/Application.NavigationTests.cs

@@ -82,7 +82,7 @@ public class ApplicationNavigationTests (ITestOutputHelper output)
     {
         IApplication app = Application.Create ();
 
-        app.Current = new ()
+        app.TopRunnable = new ()
         {
             Id = "top",
             CanFocus = true,
@@ -101,10 +101,10 @@ public class ApplicationNavigationTests (ITestOutputHelper output)
             CanFocus = true
         };
 
-        app.Current?.Add (subView1, subView2);
-        Assert.False (app.Current?.HasFocus);
+        app.TopRunnable?.Add (subView1, subView2);
+        Assert.False (app.TopRunnable?.HasFocus);
 
-        app.Current?.SetFocus ();
+        app.TopRunnable?.SetFocus ();
         Assert.True (subView1.HasFocus);
         Assert.Equal (subView1, app.Navigation?.GetFocused ());
 
@@ -117,7 +117,7 @@ public class ApplicationNavigationTests (ITestOutputHelper output)
     {
         IApplication app = Application.Create ();
 
-        app.Current = new ()
+        app.TopRunnable = new ()
         {
             Id = "top",
             CanFocus = true,
@@ -130,20 +130,20 @@ public class ApplicationNavigationTests (ITestOutputHelper output)
             CanFocus = true
         };
 
-        app!.Current.Add (subView1);
-        Assert.False (app.Current.HasFocus);
+        app!.TopRunnable.Add (subView1);
+        Assert.False (app.TopRunnable.HasFocus);
 
-        app.Current.SetFocus ();
+        app.TopRunnable.SetFocus ();
         Assert.True (subView1.HasFocus);
         Assert.Equal (subView1, app.Navigation!.GetFocused ());
 
         subView1.HasFocus = false;
         Assert.False (subView1.HasFocus);
-        Assert.True (app.Current.HasFocus);
-        Assert.Equal (app.Current, app.Navigation.GetFocused ());
+        Assert.True (app.TopRunnable.HasFocus);
+        Assert.Equal (app.TopRunnable, app.Navigation.GetFocused ());
 
-        app.Current.HasFocus = false;
-        Assert.False (app.Current.HasFocus);
+        app.TopRunnable.HasFocus = false;
+        Assert.False (app.TopRunnable.HasFocus);
         Assert.Null (app.Navigation.GetFocused ());
 
     }

+ 14 - 14
Tests/UnitTests/Application/ApplicationImplBeginEndTests.cs

@@ -45,12 +45,12 @@ public class ApplicationImplBeginEndTests
         try
         {
             toplevel = new ();
-            Assert.Null (app.Current);
+            Assert.Null (app.TopRunnable);
 
             app.Begin (toplevel);
 
-            Assert.NotNull (app.Current);
-            Assert.Same (toplevel, app.Current);
+            Assert.NotNull (app.TopRunnable);
+            Assert.Same (toplevel, app.TopRunnable);
             Assert.Single (app.SessionStack);
         }
         finally
@@ -74,11 +74,11 @@ public class ApplicationImplBeginEndTests
 
             app.Begin (toplevel1);
             Assert.Single (app.SessionStack);
-            Assert.Same (toplevel1, app.Current);
+            Assert.Same (toplevel1, app.TopRunnable);
 
             app.Begin (toplevel2);
             Assert.Equal (2, app.SessionStack.Count);
-            Assert.Same (toplevel2, app.Current);
+            Assert.Same (toplevel2, app.TopRunnable);
         }
         finally
         {
@@ -163,7 +163,7 @@ public class ApplicationImplBeginEndTests
             app.End (token2);
 
             Assert.Single (app.SessionStack);
-            Assert.Same (toplevel1, app.Current);
+            Assert.Same (toplevel1, app.TopRunnable);
 
             app.End (token1);
 
@@ -228,13 +228,13 @@ public class ApplicationImplBeginEndTests
             SessionToken token2 = app.Begin (toplevel2);
             SessionToken token3 = app.Begin (toplevel3);
 
-            Assert.Same (toplevel3, app.Current);
+            Assert.Same (toplevel3, app.TopRunnable);
 
             app.End (token3);
-            Assert.Same (toplevel2, app.Current);
+            Assert.Same (toplevel2, app.TopRunnable);
 
             app.End (token2);
-            Assert.Same (toplevel1, app.Current);
+            Assert.Same (toplevel1, app.TopRunnable);
 
             app.End (token1);
         }
@@ -265,7 +265,7 @@ public class ApplicationImplBeginEndTests
             }
 
             Assert.Equal (5, app.SessionStack.Count);
-            Assert.Same (toplevels [4], app.Current);
+            Assert.Same (toplevels [4], app.TopRunnable);
 
             // End them in reverse order (LIFO)
             for (var i = 4; i >= 0; i--)
@@ -275,7 +275,7 @@ public class ApplicationImplBeginEndTests
                 if (i > 0)
                 {
                     Assert.Equal (i, app.SessionStack.Count);
-                    Assert.Same (toplevels [i - 1], app.Current);
+                    Assert.Same (toplevels [i - 1], app.TopRunnable);
                 }
                 else
                 {
@@ -358,7 +358,7 @@ public class ApplicationImplBeginEndTests
             app.Begin (toplevel2);
 
             Assert.Equal (2, app.SessionStack.Count);
-            Assert.NotNull (app.Current);
+            Assert.NotNull (app.TopRunnable);
         }
         finally
         {
@@ -371,7 +371,7 @@ public class ApplicationImplBeginEndTests
 
             // Verify cleanup happened
             Assert.Empty (app.SessionStack);
-            Assert.Null (app.Current);
+            Assert.Null (app.TopRunnable);
             Assert.Null (app.CachedSessionTokenToplevel);
         }
     }
@@ -432,7 +432,7 @@ public class ApplicationImplBeginEndTests
 
             Assert.True (toplevel1Deactivated);
             Assert.True (toplevel2Activated);
-            Assert.Same (toplevel2, app.Current);
+            Assert.Same (toplevel2, app.TopRunnable);
         }
         finally
         {

+ 31 - 31
Tests/UnitTests/Application/ApplicationImplTests.cs

@@ -81,7 +81,7 @@ public class ApplicationImplTests
                                                 TimeSpan.FromMilliseconds (150),
                                                 () =>
                                                 {
-                                                    if (app.Current is { })
+                                                    if (app.TopRunnable is { })
                                                     {
                                                         app.RequestStop ();
 
@@ -91,7 +91,7 @@ public class ApplicationImplTests
                                                     return false;
                                                 }
                                                );
-        Assert.Null (app?.Current);
+        Assert.Null (app?.TopRunnable);
 
         // Blocks until the timeout call is hit
 
@@ -100,10 +100,10 @@ public class ApplicationImplTests
         // We returned false above, so we should not have to remove the timeout
         Assert.False (app?.RemoveTimeout (timeoutToken!));
 
-        Assert.NotNull (app?.Current);
-        app.Current?.Dispose ();
+        Assert.NotNull (app?.TopRunnable);
+        app.TopRunnable?.Dispose ();
         app.Shutdown ();
-        Assert.Null (app.Current);
+        Assert.Null (app.TopRunnable);
     }
 
     [Fact]
@@ -124,7 +124,7 @@ public class ApplicationImplTests
                                               {
                                                   Assert.True (top!.Running);
 
-                                                  if (app.Current != null)
+                                                  if (app.TopRunnable != null)
                                                   {
                                                       app.RequestStop ();
 
@@ -146,8 +146,8 @@ public class ApplicationImplTests
         Assert.False (top!.Running);
 
         // BUGBUG: Shutdown sets Top to null, not End.
-        //Assert.Null (Application.Current);
-        app.Current?.Dispose ();
+        //Assert.Null (Application.TopRunnable);
+        app.TopRunnable?.Dispose ();
         app.Shutdown ();
     }
 
@@ -156,13 +156,13 @@ public class ApplicationImplTests
     {
         IApplication app = NewMockedApplicationImpl ()!;
 
-        Assert.Null (app.Current);
+        Assert.Null (app.TopRunnable);
         Assert.Null (app.Driver);
 
         app.Init ("fake");
 
         Toplevel top = new Window ();
-        app.Current = top;
+        app.TopRunnable = top;
 
         var closedCount = 0;
 
@@ -193,7 +193,7 @@ public class ApplicationImplTests
         Assert.Equal (1, closedCount);
         Assert.Equal (1, unloadedCount);
 
-        app.Current?.Dispose ();
+        app.TopRunnable?.Dispose ();
         app.Shutdown ();
         Assert.Equal (1, closedCount);
         Assert.Equal (1, unloadedCount);
@@ -204,7 +204,7 @@ public class ApplicationImplTests
     {
         IApplication app = NewMockedApplicationImpl ()!;
 
-        Assert.Null (app.Current);
+        Assert.Null (app.TopRunnable);
         Assert.Null (app.Driver);
 
         app.Init ("fake");
@@ -228,7 +228,7 @@ public class ApplicationImplTests
                                               {
                                                   Assert.True (top!.Running);
 
-                                                  if (app.Current != null)
+                                                  if (app.TopRunnable != null)
                                                   {
                                                       app.RequestStop ();
 
@@ -251,7 +251,7 @@ public class ApplicationImplTests
         // We returned false above, so we should not have to remove the timeout
         Assert.False (app.RemoveTimeout (timeoutToken));
 
-        app.Current?.Dispose ();
+        app.TopRunnable?.Dispose ();
         app.Shutdown ();
         Assert.Equal (1, closedCount);
         Assert.Equal (1, unloadedCount);
@@ -275,7 +275,7 @@ public class ApplicationImplTests
                                               {
                                                   Assert.True (top!.Running);
 
-                                                  if (app.Current != null)
+                                                  if (app.TopRunnable != null)
                                                   {
                                                       app.Keyboard.RaiseKeyDownEvent (app.Keyboard.QuitKey);
                                                   }
@@ -294,10 +294,10 @@ public class ApplicationImplTests
 
         Assert.False (top!.Running);
 
-        Assert.NotNull (app.Current);
+        Assert.NotNull (app.TopRunnable);
         top.Dispose ();
         app.Shutdown ();
-        Assert.Null (app.Current);
+        Assert.Null (app.TopRunnable);
     }
 
     [Fact]
@@ -308,16 +308,16 @@ public class ApplicationImplTests
         app.Init ("fake");
 
         app.AddTimeout (TimeSpan.Zero, () => IdleExit (app));
-        Assert.Null (app.Current);
+        Assert.Null (app.TopRunnable);
 
         // Blocks until the timeout call is hit
 
         app.Run<Window> ();
 
-        Assert.NotNull (app.Current);
-        app.Current?.Dispose ();
+        Assert.NotNull (app.TopRunnable);
+        app.TopRunnable?.Dispose ();
         app.Shutdown ();
-        Assert.Null (app.Current);
+        Assert.Null (app.TopRunnable);
     }
 
     [Fact]
@@ -357,7 +357,7 @@ public class ApplicationImplTests
 
         app.Run (t);
 
-        app.Current?.Dispose ();
+        app.TopRunnable?.Dispose ();
         app.Shutdown ();
 
         Assert.Equal (2, closing);
@@ -366,7 +366,7 @@ public class ApplicationImplTests
 
     private bool IdleExit (IApplication app)
     {
-        if (app.Current != null)
+        if (app.TopRunnable != null)
         {
             app.RequestStop ();
 
@@ -408,7 +408,7 @@ public class ApplicationImplTests
                         () =>
                         {
                             // Run asynchronous logic inside Task.Run
-                            if (app.Current != null)
+                            if (app.TopRunnable != null)
                             {
                                 b.NewKeyDownEvent (Key.Enter);
                                 b.NewKeyUpEvent (Key.Enter);
@@ -417,7 +417,7 @@ public class ApplicationImplTests
                             return false;
                         });
 
-        Assert.Null (app.Current);
+        Assert.Null (app.TopRunnable);
 
         var w = new Window
         {
@@ -428,10 +428,10 @@ public class ApplicationImplTests
         // Blocks until the timeout call is hit
         app.Run (w);
 
-        Assert.NotNull (app.Current);
-        app.Current?.Dispose ();
+        Assert.NotNull (app.TopRunnable);
+        app.TopRunnable?.Dispose ();
         app.Shutdown ();
-        Assert.Null (app.Current);
+        Assert.Null (app.TopRunnable);
 
         Assert.True (result);
     }
@@ -448,7 +448,7 @@ public class ApplicationImplTests
 
         //Assert.Null (v2.Popover);
         //Assert.Null (v2.Navigation);
-        Assert.Null (v2.Current);
+        Assert.Null (v2.TopRunnable);
         Assert.Empty (v2.SessionStack);
 
         // Init should populate instance fields
@@ -459,7 +459,7 @@ public class ApplicationImplTests
         Assert.True (v2.Initialized);
         Assert.NotNull (v2.Popover);
         Assert.NotNull (v2.Navigation);
-        Assert.Null (v2.Current); // Top is still null until Run
+        Assert.Null (v2.TopRunnable); // Top is still null until Run
 
         // Shutdown should clean up instance fields
         v2.Shutdown ();
@@ -469,7 +469,7 @@ public class ApplicationImplTests
 
         //Assert.Null (v2.Popover);
         //Assert.Null (v2.Navigation);
-        Assert.Null (v2.Current);
+        Assert.Null (v2.TopRunnable);
         Assert.Empty (v2.SessionStack);
     }
 }

+ 7 - 7
Tests/UnitTests/Application/ApplicationPopoverTests.cs

@@ -197,14 +197,14 @@ public class ApplicationPopoverTests
             // Arrange
 
             Application.Init ("fake");
-            Application.Current = new ();
+            Application.TopRunnable = new ();
             PopoverTestClass? popover = new ();
 
             // Act
             Application.Popover?.Register (popover);
 
             // Assert
-            Assert.Equal (Application.Current, popover.Current);
+            Assert.Equal (Application.TopRunnable, popover.Current);
         }
         finally
         {
@@ -219,7 +219,7 @@ public class ApplicationPopoverTests
         {
             // Arrange
             Application.Init ("fake");
-            Application.Current = new() { Id = "initialTop" };
+            Application.TopRunnable = new() { Id = "initialTop" };
             PopoverTestClass? popover = new () { };
             var keyDownEvents = 0;
 
@@ -234,7 +234,7 @@ public class ApplicationPopoverTests
             // Act
             Application.RaiseKeyDownEvent (Key.A); // Goes to initialTop
 
-            Application.Current = new() { Id = "secondaryTop" };
+            Application.TopRunnable = new() { Id = "secondaryTop" };
             Application.RaiseKeyDownEvent (Key.A); // Goes to secondaryTop
 
             // Test
@@ -267,7 +267,7 @@ public class ApplicationPopoverTests
             // Arrange
             Application.Init ("fake");
 
-            Application.Current = new ()
+            Application.TopRunnable = new ()
             {
                 Frame = new (0, 0, 10, 10),
                 Id = "top"
@@ -282,7 +282,7 @@ public class ApplicationPopoverTests
                 Height = 2
             };
 
-            Application.Current.Add (view);
+            Application.TopRunnable.Add (view);
 
             popover = new ()
             {
@@ -316,7 +316,7 @@ public class ApplicationPopoverTests
         finally
         {
             popover?.Dispose ();
-            Application.Current?.Dispose ();
+            Application.TopRunnable?.Dispose ();
             Application.ResetState (true);
         }
     }

+ 5 - 5
Tests/UnitTests/Application/ApplicationScreenTests.cs

@@ -46,35 +46,35 @@ public class ApplicationScreenTests
         Assert.Equal (0, clearedContentsRaised);
 
         // Act
-        Application.Current!.SetNeedsLayout ();
+        Application.TopRunnable!.SetNeedsLayout ();
         Application.LayoutAndDraw ();
 
         // Assert
         Assert.Equal (0, clearedContentsRaised);
 
         // Act
-        Application.Current.X = 1;
+        Application.TopRunnable.X = 1;
         Application.LayoutAndDraw ();
 
         // Assert
         Assert.Equal (1, clearedContentsRaised);
 
         // Act
-        Application.Current.Width = 10;
+        Application.TopRunnable.Width = 10;
         Application.LayoutAndDraw ();
 
         // Assert
         Assert.Equal (2, clearedContentsRaised);
 
         // Act
-        Application.Current.Y = 1;
+        Application.TopRunnable.Y = 1;
         Application.LayoutAndDraw ();
 
         // Assert
         Assert.Equal (3, clearedContentsRaised);
 
         // Act
-        Application.Current.Height = 10;
+        Application.TopRunnable.Height = 10;
         Application.LayoutAndDraw ();
 
         // Assert

+ 55 - 55
Tests/UnitTests/Application/ApplicationTests.cs

@@ -70,13 +70,13 @@ public class ApplicationTests
     [SetupFakeApplication]
     public void Begin_Sets_Application_Top_To_Console_Size ()
     {
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
         Application.Driver!.SetScreenSize (80, 25);
         Toplevel top = new ();
         Application.Begin (top);
-        Assert.Equal (new (0, 0, 80, 25), Application.Current!.Frame);
+        Assert.Equal (new (0, 0, 80, 25), Application.TopRunnable!.Frame);
         Application.Driver!.SetScreenSize (5, 5);
-        Assert.Equal (new (0, 0, 5, 5), Application.Current!.Frame);
+        Assert.Equal (new (0, 0, 5, 5), Application.TopRunnable!.Frame);
         top.Dispose ();
     }
 
@@ -84,21 +84,21 @@ public class ApplicationTests
     [SetupFakeApplication]
     public void End_And_Shutdown_Should_Not_Dispose_ApplicationTop ()
     {
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
 
         SessionToken rs = Application.Begin (new ());
-        Application.Current!.Title = "End_And_Shutdown_Should_Not_Dispose_ApplicationTop";
-        Assert.Equal (rs.Toplevel, Application.Current);
+        Application.TopRunnable!.Title = "End_And_Shutdown_Should_Not_Dispose_ApplicationTop";
+        Assert.Equal (rs.Toplevel, Application.TopRunnable);
         Application.End (rs);
 
 #if DEBUG_IDISPOSABLE
         Assert.True (rs.WasDisposed);
-        Assert.False (Application.Current!.WasDisposed); // Is true because the rs.Toplevel is the same as Application.Current
+        Assert.False (Application.TopRunnable!.WasDisposed); // Is true because the rs.Toplevel is the same as Application.TopRunnable
 #endif
 
         Assert.Null (rs.Toplevel);
 
-        Toplevel top = Application.Current;
+        Toplevel top = Application.TopRunnable;
 
 #if DEBUG_IDISPOSABLE
         Exception exception = Record.Exception (Application.Shutdown);
@@ -132,12 +132,12 @@ public class ApplicationTests
         Assert.NotNull (sessionToken);
         Assert.Equal (rs, sessionToken);
 
-        Assert.Equal (topLevel, Application.Current);
+        Assert.Equal (topLevel, Application.TopRunnable);
 
         Application.SessionBegun -= newSessionTokenFn;
         Application.End (sessionToken);
 
-        Assert.NotNull (Application.Current);
+        Assert.NotNull (Application.TopRunnable);
         Assert.NotNull (Application.Driver);
 
         topLevel.Dispose ();
@@ -246,7 +246,7 @@ public class ApplicationTests
             // Check that all fields and properties are set to their default values
 
             // Public Properties
-            Assert.Null (Application.Current);
+            Assert.Null (Application.TopRunnable);
             Assert.Null (Application.Mouse.MouseGrabView);
 
             // Don't check Application.ForceDriver
@@ -391,18 +391,18 @@ public class ApplicationTests
         Assert.NotNull (sessionToken);
         Assert.Equal (rs, sessionToken);
 
-        Assert.Equal (topLevel, Application.Current);
+        Assert.Equal (topLevel, Application.TopRunnable);
 
         Application.SessionBegun -= newSessionTokenFn;
         Application.End (sessionToken);
 
-        Assert.NotNull (Application.Current);
+        Assert.NotNull (Application.TopRunnable);
         Assert.NotNull (Application.Driver);
 
         topLevel.Dispose ();
         Application.Shutdown ();
 
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
         Assert.Null (Application.Driver);
     }
 
@@ -411,11 +411,11 @@ public class ApplicationTests
     public void Internal_Properties_Correct ()
     {
         Assert.True (Application.Initialized);
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
         SessionToken rs = Application.Begin (new ());
-        Assert.Equal (Application.Current, rs.Toplevel);
+        Assert.Equal (Application.TopRunnable, rs.Toplevel);
         Assert.Null (Application.Mouse.MouseGrabView); // public
-        Application.Current!.Dispose ();
+        Application.TopRunnable!.Dispose ();
     }
 
     // Invoke Tests
@@ -511,9 +511,9 @@ public class ApplicationTests
         // Run<Toplevel> when already initialized or not with a Driver will not throw (because Window is derived from Toplevel)
         // Using another type not derived from Toplevel will throws at compile time
         Application.Run<Window> ();
-        Assert.True (Application.Current is Window);
+        Assert.True (Application.TopRunnable is Window);
 
-        Application.Current!.Dispose ();
+        Application.TopRunnable!.Dispose ();
     }
 
     [Fact]
@@ -524,15 +524,15 @@ public class ApplicationTests
         // Run<Toplevel> when already initialized or not with a Driver will not throw (because Window is derived from Toplevel)
         // Using another type not derived from Toplevel will throws at compile time
         Application.Run<Window> (null, "fake");
-        Assert.True (Application.Current is Window);
+        Assert.True (Application.TopRunnable is Window);
 
-        Application.Current!.Dispose ();
+        Application.TopRunnable!.Dispose ();
 
         // Run<Toplevel> when already initialized or not with a Driver will not throw (because Dialog is derived from Toplevel)
         Application.Run<Dialog> (null, "fake");
-        Assert.True (Application.Current is Dialog);
+        Assert.True (Application.TopRunnable is Dialog);
 
-        Application.Current!.Dispose ();
+        Application.TopRunnable!.Dispose ();
         Application.Shutdown ();
     }
 
@@ -540,7 +540,7 @@ public class ApplicationTests
     [SetupFakeApplication]
     public void Run_T_After_Init_Does_Not_Disposes_Application_Top ()
     {
-        // Init doesn't create a Toplevel and assigned it to Application.Current
+        // Init doesn't create a Toplevel and assigned it to Application.TopRunnable
         // but Begin does
         var initTop = new Toplevel ();
 
@@ -554,13 +554,13 @@ public class ApplicationTests
         initTop.Dispose ();
         Assert.True (initTop.WasDisposed);
 #endif
-        Application.Current!.Dispose ();
+        Application.TopRunnable!.Dispose ();
 
         return;
 
         void OnApplicationOnIteration (object s, IterationEventArgs a)
         {
-            Assert.NotEqual (initTop, Application.Current);
+            Assert.NotEqual (initTop, Application.TopRunnable);
 #if DEBUG_IDISPOSABLE
             Assert.False (initTop.WasDisposed);
 #endif
@@ -577,7 +577,7 @@ public class ApplicationTests
         // Init has been called and we're passing no driver to Run<TestTopLevel>. This is ok.
         Application.Run<Toplevel> ();
 
-        Application.Current!.Dispose ();
+        Application.TopRunnable!.Dispose ();
     }
 
     [Fact]
@@ -589,7 +589,7 @@ public class ApplicationTests
         // Init has been called, selecting FakeDriver; we're passing no driver to Run<TestTopLevel>. Should be fine.
         Application.Run<Toplevel> ();
 
-        Application.Current!.Dispose ();
+        Application.TopRunnable!.Dispose ();
     }
 
     [Fact]
@@ -620,7 +620,7 @@ public class ApplicationTests
         // Init has NOT been called and we're passing a valid driver to Run<TestTopLevel>. This is ok.
         Application.Run<Toplevel> (null, "fake");
 
-        Application.Current!.Dispose ();
+        Application.TopRunnable!.Dispose ();
     }
 
     [Fact]
@@ -744,9 +744,9 @@ public class ApplicationTests
 
         Assert.NotNull (w);
         Assert.Equal (string.Empty, w.Title); // Valid - w has not been disposed. The user may want to run it again
-        Assert.NotNull (Application.Current);
-        Assert.Equal (w, Application.Current);
-        Assert.NotEqual (top, Application.Current);
+        Assert.NotNull (Application.TopRunnable);
+        Assert.Equal (w, Application.TopRunnable);
+        Assert.NotEqual (top, Application.TopRunnable);
 
         Application.Run (w); // Valid - w has not been disposed.
 
@@ -774,14 +774,14 @@ public class ApplicationTests
     [Fact]
     public void Run_Creates_Top_Without_Init ()
     {
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
         Application.StopAfterFirstIteration = true;
 
         Application.Iteration += OnApplicationOnIteration;
         Toplevel top = Application.Run (null, "fake");
         Application.Iteration -= OnApplicationOnIteration;
 #if DEBUG_IDISPOSABLE
-        Assert.Equal (top, Application.Current);
+        Assert.Equal (top, Application.TopRunnable);
         Assert.False (top.WasDisposed);
         Exception exception = Record.Exception (Application.Shutdown);
         Assert.NotNull (exception);
@@ -794,38 +794,38 @@ public class ApplicationTests
 #if DEBUG_IDISPOSABLE
         Assert.True (top.WasDisposed);
 #endif
-        Assert.NotNull (Application.Current);
+        Assert.NotNull (Application.TopRunnable);
 
         Application.Shutdown ();
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
 
         return;
 
-        void OnApplicationOnIteration (object s, IterationEventArgs e) { Assert.NotNull (Application.Current); }
+        void OnApplicationOnIteration (object s, IterationEventArgs e) { Assert.NotNull (Application.TopRunnable); }
     }
 
     [Fact]
     public void Run_T_Creates_Top_Without_Init ()
     {
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
 
         Application.StopAfterFirstIteration = true;
 
         Application.Run<Toplevel> (null, "fake");
 #if DEBUG_IDISPOSABLE
-        Assert.False (Application.Current!.WasDisposed);
+        Assert.False (Application.TopRunnable!.WasDisposed);
         Exception exception = Record.Exception (Application.Shutdown);
         Assert.NotNull (exception);
-        Assert.False (Application.Current!.WasDisposed);
+        Assert.False (Application.TopRunnable!.WasDisposed);
 
         // It's up to caller to dispose it
-        Application.Current!.Dispose ();
-        Assert.True (Application.Current!.WasDisposed);
+        Application.TopRunnable!.Dispose ();
+        Assert.True (Application.TopRunnable!.WasDisposed);
 #endif
-        Assert.NotNull (Application.Current);
+        Assert.NotNull (Application.TopRunnable);
 
         Application.Shutdown ();
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
     }
 
     [Fact]
@@ -839,7 +839,7 @@ public class ApplicationTests
         // the new(Toplevel) may be a derived class that is possible using Application static
         // properties that is only available after the Application.Init was called
 
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
 
         Assert.Throws<NotInitializedException> (() => Application.Run (new Toplevel ()));
 
@@ -849,25 +849,25 @@ public class ApplicationTests
         Application.Run (new Toplevel ());
         Application.Iteration -= OnApplication_OnIteration;
 #if DEBUG_IDISPOSABLE
-        Assert.False (Application.Current!.WasDisposed);
+        Assert.False (Application.TopRunnable!.WasDisposed);
         Exception exception = Record.Exception (Application.Shutdown);
         Assert.NotNull (exception);
-        Assert.False (Application.Current!.WasDisposed);
+        Assert.False (Application.TopRunnable!.WasDisposed);
 
         // It's up to caller to dispose it
-        Application.Current!.Dispose ();
-        Assert.True (Application.Current!.WasDisposed);
+        Application.TopRunnable!.Dispose ();
+        Assert.True (Application.TopRunnable!.WasDisposed);
 #endif
-        Assert.NotNull (Application.Current);
+        Assert.NotNull (Application.TopRunnable);
 
         Application.Shutdown ();
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
 
         return;
 
         void OnApplication_OnIteration (object s, IterationEventArgs e)
         {
-            Assert.NotNull (Application.Current);
+            Assert.NotNull (Application.TopRunnable);
             Application.RequestStop ();
         }
     }
@@ -892,9 +892,9 @@ public class ApplicationTests
                            TaskScheduler.FromCurrentSynchronizationContext ());
         Application.Run<TestToplevel> ();
         Assert.NotNull (Application.Driver);
-        Assert.NotNull (Application.Current);
-        Assert.False (Application.Current!.Running);
-        Application.Current!.Dispose ();
+        Assert.NotNull (Application.TopRunnable);
+        Assert.False (Application.TopRunnable!.Running);
+        Application.TopRunnable!.Dispose ();
         Application.Shutdown ();
     }
 

+ 39 - 39
Tests/UnitTests/Application/Mouse/ApplicationMouseEnterLeaveTests.cs

@@ -41,9 +41,9 @@ public class ApplicationMouseEnterLeaveTests
     public void RaiseMouseEnterLeaveEvents_MouseEntersView_CallsOnMouseEnter ()
     {
         // Arrange
-        Application.Current = new () { Frame = new (0, 0, 10, 10) };
+        Application.TopRunnable = new () { Frame = new (0, 0, 10, 10) };
         var view = new TestView ();
-        Application.Current.Add (view);
+        Application.TopRunnable.Add (view);
         var mousePosition = new Point (1, 1);
         List<View> currentViewsUnderMouse = new () { view };
 
@@ -66,7 +66,7 @@ public class ApplicationMouseEnterLeaveTests
         finally
         {
             // Cleanup
-            Application.Current?.Dispose ();
+            Application.TopRunnable?.Dispose ();
             Application.ResetState ();
         }
     }
@@ -75,9 +75,9 @@ public class ApplicationMouseEnterLeaveTests
     public void RaiseMouseEnterLeaveEvents_MouseLeavesView_CallsOnMouseLeave ()
     {
         // Arrange
-        Application.Current = new () { Frame = new (0, 0, 10, 10) };
+        Application.TopRunnable = new () { Frame = new (0, 0, 10, 10) };
         var view = new TestView ();
-        Application.Current.Add (view);
+        Application.TopRunnable.Add (view);
         var mousePosition = new Point (0, 0);
         List<View> currentViewsUnderMouse = new ();
         var mouseEvent = new MouseEventArgs ();
@@ -97,7 +97,7 @@ public class ApplicationMouseEnterLeaveTests
         finally
         {
             // Cleanup
-            Application.Current?.Dispose ();
+            Application.TopRunnable?.Dispose ();
             Application.ResetState ();
         }
     }
@@ -106,7 +106,7 @@ public class ApplicationMouseEnterLeaveTests
     public void RaiseMouseEnterLeaveEvents_MouseMovesBetweenAdjacentViews_CallsOnMouseEnterAndLeave ()
     {
         // Arrange
-        Application.Current = new () { Frame = new (0, 0, 10, 10) };
+        Application.TopRunnable = new () { Frame = new (0, 0, 10, 10) };
         var view1 = new TestView (); // at 1,1 to 2,2
 
         var view2 = new TestView () // at 2,2 to 3,3
@@ -114,8 +114,8 @@ public class ApplicationMouseEnterLeaveTests
             X = 2,
             Y = 2
         };
-        Application.Current.Add (view1);
-        Application.Current.Add (view2);
+        Application.TopRunnable.Add (view1);
+        Application.TopRunnable.Add (view2);
 
         Application.CachedViewsUnderMouse.Clear ();
 
@@ -126,7 +126,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (0, view1.OnMouseEnterCalled);
@@ -139,7 +139,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -152,7 +152,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -165,7 +165,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -178,7 +178,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -189,7 +189,7 @@ public class ApplicationMouseEnterLeaveTests
         finally
         {
             // Cleanup
-            Application.Current?.Dispose ();
+            Application.TopRunnable?.Dispose ();
             Application.ResetState ();
         }
     }
@@ -198,9 +198,9 @@ public class ApplicationMouseEnterLeaveTests
     public void RaiseMouseEnterLeaveEvents_NoViewsUnderMouse_DoesNotCallOnMouseEnterOrLeave ()
     {
         // Arrange
-        Application.Current = new () { Frame = new (0, 0, 10, 10) };
+        Application.TopRunnable = new () { Frame = new (0, 0, 10, 10) };
         var view = new TestView ();
-        Application.Current.Add (view);
+        Application.TopRunnable.Add (view);
         var mousePosition = new Point (0, 0);
         List<View> currentViewsUnderMouse = new ();
         var mouseEvent = new MouseEventArgs ();
@@ -219,7 +219,7 @@ public class ApplicationMouseEnterLeaveTests
         finally
         {
             // Cleanup
-            Application.Current?.Dispose ();
+            Application.TopRunnable?.Dispose ();
             Application.ResetState ();
         }
     }
@@ -228,7 +228,7 @@ public class ApplicationMouseEnterLeaveTests
     public void RaiseMouseEnterLeaveEvents_MouseMovesBetweenOverlappingPeerViews_CallsOnMouseEnterAndLeave ()
     {
         // Arrange
-        Application.Current = new () { Frame = new (0, 0, 10, 10) };
+        Application.TopRunnable = new () { Frame = new (0, 0, 10, 10) };
 
         var view1 = new TestView
         {
@@ -241,8 +241,8 @@ public class ApplicationMouseEnterLeaveTests
             X = 2,
             Y = 2
         };
-        Application.Current.Add (view1);
-        Application.Current.Add (view2);
+        Application.TopRunnable.Add (view1);
+        Application.TopRunnable.Add (view2);
 
         Application.CachedViewsUnderMouse.Clear ();
 
@@ -253,7 +253,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (0, view1.OnMouseEnterCalled);
@@ -266,7 +266,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -279,7 +279,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -292,7 +292,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -305,7 +305,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -318,7 +318,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -329,7 +329,7 @@ public class ApplicationMouseEnterLeaveTests
         finally
         {
             // Cleanup
-            Application.Current?.Dispose ();
+            Application.TopRunnable?.Dispose ();
             Application.ResetState ();
         }
     }
@@ -338,7 +338,7 @@ public class ApplicationMouseEnterLeaveTests
     public void RaiseMouseEnterLeaveEvents_MouseMovesBetweenOverlappingSubViews_CallsOnMouseEnterAndLeave ()
     {
         // Arrange
-        Application.Current = new () { Frame = new (0, 0, 10, 10) };
+        Application.TopRunnable = new () { Frame = new (0, 0, 10, 10) };
 
         var view1 = new TestView
         {
@@ -358,7 +358,7 @@ public class ApplicationMouseEnterLeaveTests
             Arrangement = ViewArrangement.Overlapped
         }; // at 2,2 to 4,4 (screen)
         view1.Add (subView);
-        Application.Current.Add (view1);
+        Application.TopRunnable.Add (view1);
 
         Application.CachedViewsUnderMouse.Clear ();
 
@@ -372,7 +372,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (0, view1.OnMouseEnterCalled);
@@ -385,7 +385,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -398,7 +398,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -411,7 +411,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (1, view1.OnMouseEnterCalled);
@@ -424,7 +424,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (2, view1.OnMouseEnterCalled);
@@ -437,7 +437,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (2, view1.OnMouseEnterCalled);
@@ -450,7 +450,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (2, view1.OnMouseEnterCalled);
@@ -463,7 +463,7 @@ public class ApplicationMouseEnterLeaveTests
 
             Application.RaiseMouseEnterLeaveEvents (
                                                     mousePosition,
-                                                    Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
+                                                    Application.TopRunnable.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse));
 
             // Assert
             Assert.Equal (3, view1.OnMouseEnterCalled);
@@ -474,7 +474,7 @@ public class ApplicationMouseEnterLeaveTests
         finally
         {
             // Cleanup
-            Application.Current?.Dispose ();
+            Application.TopRunnable?.Dispose ();
             Application.ResetState ();
         }
     }

+ 8 - 8
Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs

@@ -202,15 +202,15 @@ public class ApplicationMouseTests
 
         var clicked = false;
 
-        Application.Current = new Toplevel ()
+        Application.TopRunnable = new Toplevel ()
         {
             Id = "top",
         };
-        Application.Current.X = 0;
-        Application.Current.Y = 0;
-        Application.Current.Width = size.Width * 2;
-        Application.Current.Height = size.Height * 2;
-        Application.Current.BorderStyle = LineStyle.None;
+        Application.TopRunnable.X = 0;
+        Application.TopRunnable.Y = 0;
+        Application.TopRunnable.Width = size.Width * 2;
+        Application.TopRunnable.Height = size.Height * 2;
+        Application.TopRunnable.BorderStyle = LineStyle.None;
 
         var view = new View { Id = "view", X = pos.X, Y = pos.Y, Width = size.Width, Height = size.Height };
 
@@ -218,7 +218,7 @@ public class ApplicationMouseTests
         view.BorderStyle = LineStyle.Single;
         view.CanFocus = true;
 
-        Application.Current.Add (view);
+        Application.TopRunnable.Add (view);
 
         var mouseEvent = new MouseEventArgs { Position = new (clickX, clickY), ScreenPosition = new (clickX, clickY), Flags = MouseFlags.Button1Clicked };
 
@@ -231,7 +231,7 @@ public class ApplicationMouseTests
 
         Application.RaiseMouseEvent (mouseEvent);
         Assert.Equal (expectedClicked, clicked);
-        Application.Current.Dispose ();
+        Application.TopRunnable.Dispose ();
         Application.ResetState (ignoreDisposed: true);
 
     }

+ 1 - 1
Tests/UnitTests/Application/SessionTokenTests.cs

@@ -29,7 +29,7 @@ public class SessionTokenTests
         Assert.NotNull (rs);
         Application.End (rs);
 
-        Assert.NotNull (Application.Current);
+        Assert.NotNull (Application.TopRunnable);
 
         // v2 does not use main loop, it uses MainLoop<T> and its internal
         //Assert.NotNull (Application.MainLoop);

+ 2 - 2
Tests/UnitTests/Application/SynchronizatonContextTests.cs

@@ -39,7 +39,7 @@ public class SyncrhonizationContextTests
 
             Task.Run (() =>
                       {
-                          while (Application.Current is null || Application.Current is { Running: false })
+                          while (Application.TopRunnable is null || Application.TopRunnable is { Running: false })
                           {
                               Thread.Sleep (500);
                           }
@@ -56,7 +56,7 @@ public class SyncrhonizationContextTests
                                         null
                                        );
 
-                          if (Application.Current is { Running: true })
+                          if (Application.TopRunnable is { Running: true })
                           {
                               Assert.False (success);
                           }

+ 15 - 15
Tests/UnitTests/Dialogs/DialogTests.cs

@@ -902,8 +902,8 @@ public class DialogTests (ITestOutputHelper output)
 
 #if DEBUG_IDISPOSABLE
         Assert.False (dlg.WasDisposed);
-        Assert.False (Application.Current!.WasDisposed);
-        Assert.Equal (dlg, Application.Current);
+        Assert.False (Application.TopRunnable!.WasDisposed);
+        Assert.Equal (dlg, Application.TopRunnable);
 #endif
 
         Assert.True (dlg.Canceled);
@@ -925,8 +925,8 @@ public class DialogTests (ITestOutputHelper output)
         Application.Run (dlg2);
 
         Assert.True (dlg.WasDisposed);
-        Assert.False (Application.Current.WasDisposed);
-        Assert.Equal (dlg2, Application.Current);
+        Assert.False (Application.TopRunnable.WasDisposed);
+        Assert.Equal (dlg2, Application.TopRunnable);
         Assert.False (dlg2.WasDisposed);
 
         dlg2.Dispose ();
@@ -937,10 +937,10 @@ public class DialogTests (ITestOutputHelper output)
         //Assert.NotNull (exception);
         //Assert.StartsWith ("Cannot access a disposed object.", exception.Message);
 
-        Assert.True (Application.Current.WasDisposed);
+        Assert.True (Application.TopRunnable.WasDisposed);
         Application.Shutdown ();
         Assert.True (dlg2.WasDisposed);
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
 #endif
 
         return;
@@ -1174,8 +1174,8 @@ public class DialogTests (ITestOutputHelper output)
             switch (iterations)
             {
                 case 0:
-                    Application.Current!.SetNeedsLayout ();
-                    Application.Current.SetNeedsDraw ();
+                    Application.TopRunnable!.SetNeedsLayout ();
+                    Application.TopRunnable.SetNeedsDraw ();
 
                     break;
 
@@ -1216,7 +1216,7 @@ public class DialogTests (ITestOutputHelper output)
   └───────────────────────┘",
                                                                    output);
 
-                    Assert.False (Application.Current!.NewKeyDownEvent (Key.Enter));
+                    Assert.False (Application.TopRunnable!.NewKeyDownEvent (Key.Enter));
 
                     break;
                 case 7:
@@ -1410,9 +1410,9 @@ public class DialogTests (ITestOutputHelper output)
 
 #if DEBUG_IDISPOSABLE
         Assert.False (dlg.WasDisposed);
-        Assert.False (Application.Current!.WasDisposed);
-        Assert.NotEqual (top, Application.Current);
-        Assert.Equal (dlg, Application.Current);
+        Assert.False (Application.TopRunnable!.WasDisposed);
+        Assert.NotEqual (top, Application.TopRunnable);
+        Assert.Equal (dlg, Application.TopRunnable);
 #endif
 
         // dlg wasn't disposed yet and it's possible to access to his properties
@@ -1426,11 +1426,11 @@ public class DialogTests (ITestOutputHelper output)
         top.Dispose ();
 #if DEBUG_IDISPOSABLE
         Assert.True (dlg.WasDisposed);
-        Assert.True (Application.Current.WasDisposed);
-        Assert.NotNull (Application.Current);
+        Assert.True (Application.TopRunnable.WasDisposed);
+        Assert.NotNull (Application.TopRunnable);
 #endif
         Application.Shutdown ();
-        Assert.Null (Application.Current);
+        Assert.Null (Application.TopRunnable);
 
         return;
 

+ 7 - 7
Tests/UnitTests/Dialogs/MessageBoxTests.cs

@@ -193,7 +193,7 @@ public class MessageBoxTests (ITestOutputHelper output)
             }
             else if (iterations == 1)
             {
-                mbFrame = Application.Current!.Frame;
+                mbFrame = Application.TopRunnable!.Frame;
                 Application.RequestStop ();
             }
         }
@@ -378,8 +378,8 @@ public class MessageBoxTests (ITestOutputHelper output)
                                      {
                                          AutoInitShutdownAttribute.RunIteration ();
 
-                                         Assert.IsType<Dialog> (Application.Current);
-                                         Assert.Equal (new (height, width), Application.Current.Frame.Size);
+                                         Assert.IsType<Dialog> (Application.TopRunnable);
+                                         Assert.Equal (new (height, width), Application.TopRunnable.Frame.Size);
 
                                          Application.RequestStop ();
                                      }
@@ -415,8 +415,8 @@ public class MessageBoxTests (ITestOutputHelper output)
                                      {
                                          AutoInitShutdownAttribute.RunIteration ();
 
-                                         Assert.IsType<Dialog> (Application.Current);
-                                         Assert.Equal (new (height, width), Application.Current.Frame.Size);
+                                         Assert.IsType<Dialog> (Application.TopRunnable);
+                                         Assert.Equal (new (height, width), Application.TopRunnable.Frame.Size);
 
                                          Application.RequestStop ();
                                      }
@@ -448,8 +448,8 @@ public class MessageBoxTests (ITestOutputHelper output)
                                      {
                                          AutoInitShutdownAttribute.RunIteration ();
 
-                                         Assert.IsType<Dialog> (Application.Current);
-                                         Assert.Equal (new (height, width), Application.Current.Frame.Size);
+                                         Assert.IsType<Dialog> (Application.TopRunnable);
+                                         Assert.Equal (new (height, width), Application.TopRunnable.Frame.Size);
 
                                          Application.RequestStop ();
                                      }

+ 13 - 18
Tests/UnitTests/DriverAssert.cs

@@ -198,7 +198,7 @@ internal partial class DriverAssert
         IDriver? driver = null
     )
     {
-        List<List<Rune>> lines = [];
+        List<List<string>> lines = [];
         var sb = new StringBuilder ();
         driver ??= Application.Driver!;
 
@@ -211,13 +211,13 @@ internal partial class DriverAssert
 
         for (var rowIndex = 0; rowIndex < driver.Rows; rowIndex++)
         {
-            List<Rune> runes = [];
+            List<string> strings = [];
 
             for (var colIndex = 0; colIndex < driver.Cols; colIndex++)
             {
-                Rune runeAtCurrentLocation = contents! [rowIndex, colIndex].Rune;
+                string textAtCurrentLocation = contents! [rowIndex, colIndex].Grapheme;
 
-                if (runeAtCurrentLocation != _spaceRune)
+                if (textAtCurrentLocation != _spaceRune.ToString ())
                 {
                     if (x == -1)
                     {
@@ -226,11 +226,11 @@ internal partial class DriverAssert
 
                         for (var i = 0; i < colIndex; i++)
                         {
-                            runes.InsertRange (i, [_spaceRune]);
+                            strings.InsertRange (i, [_spaceRune.ToString ()]);
                         }
                     }
 
-                    if (runeAtCurrentLocation.GetColumns () > 1)
+                    if (textAtCurrentLocation.GetColumns () > 1)
                     {
                         colIndex++;
                     }
@@ -245,18 +245,13 @@ internal partial class DriverAssert
 
                 if (x > -1)
                 {
-                    runes.Add (runeAtCurrentLocation);
+                    strings.Add (textAtCurrentLocation);
                 }
-
-                // See Issue #2616
-                //foreach (var combMark in contents [r, c].CombiningMarks) {
-                //	runes.Add (combMark);
-                //}
             }
 
-            if (runes.Count > 0)
+            if (strings.Count > 0)
             {
-                lines.Add (runes);
+                lines.Add (strings);
             }
         }
 
@@ -270,13 +265,13 @@ internal partial class DriverAssert
         }
 
         // Remove trailing whitespace on each line
-        foreach (List<Rune> row in lines)
+        foreach (List<string> row in lines)
         {
             for (int c = row.Count - 1; c >= 0; c--)
             {
-                Rune rune = row [c];
+                string text = row [c];
 
-                if (rune != (Rune)' ' || row.Sum (x => x.GetColumns ()) == w)
+                if (text != " " || row.Sum (x => x.GetColumns ()) == w)
                 {
                     break;
                 }
@@ -285,7 +280,7 @@ internal partial class DriverAssert
             }
         }
 
-        // Convert Rune list to string
+        // Convert Text list to string
         for (var r = 0; r < lines.Count; r++)
         {
             var line = StringExtensions.ToString (lines [r]);

+ 8 - 8
Tests/UnitTests/Drivers/ClipRegionTests.cs

@@ -16,24 +16,24 @@ public class ClipRegionTests (ITestOutputHelper output)
 
         Application.Driver!.Move (0, 0);
         Application.Driver!.AddRune ('x');
-        Assert.Equal ((Rune)'x', Application.Driver!.Contents! [0, 0].Rune);
+        Assert.Equal ("x", Application.Driver!.Contents! [0, 0].Grapheme);
 
         Application.Driver?.Move (5, 5);
         Application.Driver?.AddRune ('x');
-        Assert.Equal ((Rune)'x', Application.Driver!.Contents [5, 5].Rune);
+        Assert.Equal ("x", Application.Driver!.Contents [5, 5].Grapheme);
 
         // Clear the contents
         Application.Driver?.FillRect (new Rectangle (0, 0, Application.Driver.Rows, Application.Driver.Cols), ' ');
-        Assert.Equal ((Rune)' ', Application.Driver?.Contents [0, 0].Rune);
+        Assert.Equal (" ", Application.Driver?.Contents [0, 0].Grapheme);
 
         // Setup the region with a single rectangle, fill screen with 'x'
         Application.Driver!.Clip = new (new Rectangle (5, 5, 5, 5));
         Application.Driver.FillRect (new Rectangle (0, 0, Application.Driver.Rows, Application.Driver.Cols), 'x');
-        Assert.Equal ((Rune)' ', Application.Driver?.Contents [0, 0].Rune);
-        Assert.Equal ((Rune)' ', Application.Driver?.Contents [4, 9].Rune);
-        Assert.Equal ((Rune)'x', Application.Driver?.Contents [5, 5].Rune);
-        Assert.Equal ((Rune)'x', Application.Driver?.Contents [9, 9].Rune);
-        Assert.Equal ((Rune)' ', Application.Driver?.Contents [10, 10].Rune);
+        Assert.Equal (" ", Application.Driver?.Contents [0, 0].Grapheme);
+        Assert.Equal (" ", Application.Driver?.Contents [4, 9].Grapheme);
+        Assert.Equal ("x", Application.Driver?.Contents [5, 5].Grapheme);
+        Assert.Equal ("x", Application.Driver?.Contents [9, 9].Grapheme);
+        Assert.Equal (" ", Application.Driver?.Contents [10, 10].Grapheme);
 
         Application.Shutdown ();
     }

+ 13 - 13
Tests/UnitTests/View/Adornment/AdornmentSubViewTests.cs

@@ -14,14 +14,14 @@ public class AdornmentSubViewTests (ITestOutputHelper output)
     [InlineData (2, 1, true)]
     public void Adornment_WithSubView_Finds (int viewMargin, int subViewMargin, bool expectedFound)
     {
-        Application.Current = new Toplevel()
+        Application.TopRunnable = new Toplevel()
         {
             Width = 10,
             Height = 10
         };
-        Application.Current.Margin!.Thickness = new Thickness (viewMargin);
+        Application.TopRunnable.Margin!.Thickness = new Thickness (viewMargin);
         // Turn of TransparentMouse for the test
-        Application.Current.Margin!.ViewportSettings = ViewportSettingsFlags.None;
+        Application.TopRunnable.Margin!.ViewportSettings = ViewportSettingsFlags.None;
 
         var subView = new View ()
         {
@@ -34,26 +34,26 @@ public class AdornmentSubViewTests (ITestOutputHelper output)
         // Turn of TransparentMouse for the test
         subView.Margin!.ViewportSettings = ViewportSettingsFlags.None;
 
-        Application.Current.Margin!.Add (subView);
-        Application.Current.Layout ();
+        Application.TopRunnable.Margin!.Add (subView);
+        Application.TopRunnable.Layout ();
 
-        var foundView = Application.Current.GetViewsUnderLocation (new Point(0, 0), ViewportSettingsFlags.None).LastOrDefault ();
+        var foundView = Application.TopRunnable.GetViewsUnderLocation (new Point(0, 0), ViewportSettingsFlags.None).LastOrDefault ();
 
         bool found = foundView == subView || foundView == subView.Margin;
         Assert.Equal (expectedFound, found);
-        Application.Current.Dispose ();
+        Application.TopRunnable.Dispose ();
         Application.ResetState (ignoreDisposed: true);
     }
 
     [Fact]
     public void Adornment_WithNonVisibleSubView_Finds_Adornment ()
     {
-        Application.Current = new Toplevel ()
+        Application.TopRunnable = new Toplevel ()
         {
             Width = 10,
             Height = 10
         };
-        Application.Current.Padding.Thickness = new Thickness (1);
+        Application.TopRunnable.Padding.Thickness = new Thickness (1);
 
         var subView = new View ()
         {
@@ -63,11 +63,11 @@ public class AdornmentSubViewTests (ITestOutputHelper output)
             Height = 1,
             Visible = false
         };
-        Application.Current.Padding.Add (subView);
-        Application.Current.Layout ();
+        Application.TopRunnable.Padding.Add (subView);
+        Application.TopRunnable.Layout ();
 
-        Assert.Equal (Application.Current.Padding, Application.Current.GetViewsUnderLocation (new Point(0, 0), ViewportSettingsFlags.None).LastOrDefault ());
-        Application.Current?.Dispose ();
+        Assert.Equal (Application.TopRunnable.Padding, Application.TopRunnable.GetViewsUnderLocation (new Point(0, 0), ViewportSettingsFlags.None).LastOrDefault ());
+        Application.TopRunnable?.Dispose ();
         Application.ResetState (ignoreDisposed: true);
     }
 }

+ 16 - 16
Tests/UnitTests/View/Adornment/MarginTests.cs

@@ -15,27 +15,27 @@ public class MarginTests (ITestOutputHelper output)
         view.Margin!.Diagnostics = ViewDiagnosticFlags.Thickness;
         view.Margin.Thickness = new (1);
 
-        Application.Current = new Toplevel ();
-        Application.SessionStack.Push (Application.Current);
+        Application.TopRunnable = new Toplevel ();
+        Application.SessionStack.Push (Application.TopRunnable);
 
-        Application.Current.SetScheme (new()
+        Application.TopRunnable.SetScheme (new()
         {
             Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red)
         });
 
-        Application.Current.Add (view);
+        Application.TopRunnable.Add (view);
         Assert.Equal (ColorName16.Red, view.Margin.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ());
-        Assert.Equal (ColorName16.Red, Application.Current.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ());
+        Assert.Equal (ColorName16.Red, Application.TopRunnable.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ());
 
-        Application.Current.BeginInit ();
-        Application.Current.EndInit ();
+        Application.TopRunnable.BeginInit ();
+        Application.TopRunnable.EndInit ();
         Application.LayoutAndDraw();
 
         DriverAssert.AssertDriverContentsAre (
                                              @"",
                                              output
                                             );
-        DriverAssert.AssertDriverAttributesAre ("0", output, null, Application.Current.GetAttributeForRole (VisualRole.Normal));
+        DriverAssert.AssertDriverAttributesAre ("0", output, null, Application.TopRunnable.GetAttributeForRole (VisualRole.Normal));
 
         Application.ResetState (true);
     }
@@ -51,20 +51,20 @@ public class MarginTests (ITestOutputHelper output)
         view.Margin.Thickness = new (1);
         view.Margin.ViewportSettings = ViewportSettingsFlags.None;
 
-        Application.Current = new Toplevel ();
-        Application.SessionStack.Push (Application.Current);
+        Application.TopRunnable = new Toplevel ();
+        Application.SessionStack.Push (Application.TopRunnable);
 
-        Application.Current.SetScheme (new ()
+        Application.TopRunnable.SetScheme (new ()
         {
             Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red)
         });
 
-        Application.Current.Add (view);
+        Application.TopRunnable.Add (view);
         Assert.Equal (ColorName16.Red, view.Margin.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ());
-        Assert.Equal (ColorName16.Red, Application.Current.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ());
+        Assert.Equal (ColorName16.Red, Application.TopRunnable.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ());
 
-        Application.Current.BeginInit ();
-        Application.Current.EndInit ();
+        Application.TopRunnable.BeginInit ();
+        Application.TopRunnable.EndInit ();
         Application.LayoutAndDraw ();
 
         DriverAssert.AssertDriverContentsAre (
@@ -74,7 +74,7 @@ M M
 MMM",
                                               output
                                              );
-        DriverAssert.AssertDriverAttributesAre ("0", output, null, Application.Current.GetAttributeForRole (VisualRole.Normal));
+        DriverAssert.AssertDriverAttributesAre ("0", output, null, Application.TopRunnable.GetAttributeForRole (VisualRole.Normal));
 
         Application.ResetState (true);
     }

+ 5 - 5
Tests/UnitTests/View/Draw/ClipTests.cs

@@ -49,17 +49,17 @@ public class ClipTests (ITestOutputHelper _output)
         view.Draw ();
 
         // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen)
-        Assert.Equal ((Rune)' ', Application.Driver?.Contents! [2, 2].Rune);
+        Assert.Equal (" ", Application.Driver?.Contents! [2, 2].Grapheme);
 
         // When we exit Draw, the view is excluded from the clip. So drawing at 0,0, is not valid and is clipped.
         view.AddRune (0, 0, Rune.ReplacementChar);
-        Assert.Equal ((Rune)' ', Application.Driver?.Contents! [2, 2].Rune);
+        Assert.Equal (" ", Application.Driver?.Contents! [2, 2].Grapheme);
 
         view.AddRune (-1, -1, Rune.ReplacementChar);
-        Assert.Equal ((Rune)'P', Application.Driver?.Contents! [1, 1].Rune);
+        Assert.Equal ("P", Application.Driver?.Contents! [1, 1].Grapheme);
 
         view.AddRune (1, 1, Rune.ReplacementChar);
-        Assert.Equal ((Rune)'P', Application.Driver?.Contents! [3, 3].Rune);
+        Assert.Equal ("P", Application.Driver?.Contents! [3, 3].Grapheme);
     }
 
     [Theory]
@@ -233,7 +233,7 @@ public class ClipTests (ITestOutputHelper _output)
         //                            01 2345678901234 56 78 90 12 34 56 
         //                            │� |0123456989│� ン  ラ イ ン で  す 。
         expectedOutput = """
-                         │�│0123456789│ンラインです。
+                         │�│0123456789│ ンラインです。
                          """;
 
         DriverAssert.AssertDriverContentsWithFrameAre (expectedOutput, _output);

+ 9 - 9
Tests/UnitTests/View/Draw/DrawTests.cs

@@ -14,19 +14,19 @@ public class DrawTests (ITestOutputHelper output)
     [Trait ("Category", "Unicode")]
     public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two ()
     {
-        const string us = "\U0000f900";
+        const string s = "\U0000f900";
         var r = (Rune)0xf900;
 
-        Assert.Equal ("豈", us);
+        Assert.Equal ("豈", s);
         Assert.Equal ("豈", r.ToString ());
-        Assert.Equal (us, r.ToString ());
+        Assert.Equal (s, r.ToString ());
 
-        Assert.Equal (2, us.GetColumns ());
+        Assert.Equal (2, s.GetColumns ());
         Assert.Equal (2, r.GetColumns ());
 
-        var win = new Window { Title = us };
+        var win = new Window { Title = s };
         var view = new View { Text = r.ToString (), Height = Dim.Fill (), Width = Dim.Fill () };
-        var tf = new TextField { Text = us, Y = 1, Width = 3 };
+        var tf = new TextField { Text = s, Y = 1, Width = 3 };
         win.Add (view, tf);
         Toplevel top = new ();
         top.Add (win);
@@ -36,9 +36,9 @@ public class DrawTests (ITestOutputHelper output)
 
         const string expectedOutput = """
 
-                                      ┌┤├────┐
-                                      │
-                                      │
+                                      ┌┤├────┐
+                                      │
+                                      │
                                       └────────┘
                                       """;
         DriverAssert.AssertDriverContentsWithFrameAre (expectedOutput, output);

+ 3 - 3
Tests/UnitTests/View/Keyboard/KeyBindingsTests.cs

@@ -160,14 +160,14 @@ public class KeyBindingsTests ()
         var hotKeyRaised = false;
         var acceptRaised = false;
         var selectRaised = false;
-        Application.Current = new Toplevel ();
+        Application.TopRunnable = new Toplevel ();
         var view = new View
         {
             CanFocus = true,
             HotKeySpecifier = new Rune ('_'),
             Title = "_Test"
         };
-        Application.Current.Add (view);
+        Application.TopRunnable.Add (view);
         view.HandlingHotKey += (s, e) => hotKeyRaised = true;
         view.Accepting += (s, e) => acceptRaised = true;
         view.Selecting += (s, e) => selectRaised = true;
@@ -191,7 +191,7 @@ public class KeyBindingsTests ()
         Assert.False (acceptRaised);
         Assert.False (selectRaised);
 
-        Application.Current.Dispose ();
+        Application.TopRunnable.Dispose ();
         Application.ResetState (true);
     }
     // tests that test KeyBindingScope.Focus and KeyBindingScope.HotKey (tests for KeyBindingScope.Application are in Application/KeyboardTests.cs)

Some files were not shown because too many files changed in this diff