Prechádzať zdrojové kódy

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

Tig 2 mesiacov pred
rodič
commit
83c63a24a6
68 zmenil súbory, kde vykonal 1736 pridanie a 1396 odobranie
  1. 0 3
      Directory.Packages.props
  2. 88 0
      Examples/UICatalog/Scenarios/Threading.cs
  3. 13 133
      Terminal.Gui/App/Application.Mouse.cs
  4. 3 4
      Terminal.Gui/App/Application.Run.cs
  5. 13 4
      Terminal.Gui/App/Application.Screen.cs
  6. 65 39
      Terminal.Gui/App/Application.cd
  7. 10 12
      Terminal.Gui/App/Application.cs
  8. 28 18
      Terminal.Gui/App/ApplicationImpl.cs
  9. 13 8
      Terminal.Gui/App/IApplication.cs
  10. 0 90
      Terminal.Gui/App/ITimedEvents.cs
  11. 4 32
      Terminal.Gui/App/MainLoop.cs
  12. 9 7
      Terminal.Gui/App/MainLoopSyncContext.cs
  13. 87 0
      Terminal.Gui/App/Mouse/IMouseGrabHandler.cs
  14. 118 0
      Terminal.Gui/App/Mouse/MouseGrabHandler.cs
  15. 0 18
      Terminal.Gui/App/Timeout.cs
  16. 64 0
      Terminal.Gui/App/Timeout/ITimedEvents.cs
  17. 38 0
      Terminal.Gui/App/Timeout/LogarithmicTimeout.cs
  18. 53 0
      Terminal.Gui/App/Timeout/SmoothAcceleratingTimeout.cs
  19. 105 169
      Terminal.Gui/App/Timeout/TimedEvents.cs
  20. 33 0
      Terminal.Gui/App/Timeout/Timeout.cs
  21. 1 1
      Terminal.Gui/App/Timeout/TimeoutEventArgs.cs
  22. 1 1
      Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs
  23. 4 41
      Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs
  24. 2 2
      Terminal.Gui/Drivers/NetDriver/NetMainLoop.cs
  25. 13 13
      Terminal.Gui/Drivers/V2/ApplicationV2.cs
  26. 2 2
      Terminal.Gui/Drivers/V2/IMainLoop.cs
  27. 1 3
      Terminal.Gui/Drivers/V2/MainLoop.cs
  28. 159 174
      Terminal.Gui/Drivers/V2/V2.cd
  29. 1 58
      Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs
  30. 3 3
      Terminal.Gui/Drivers/WindowsDriver/WindowsMainLoop.cs
  31. 3 5
      Terminal.Gui/Terminal.Gui.csproj
  32. 9 9
      Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs
  33. 2 2
      Terminal.Gui/ViewBase/Adornment/Border.cs
  34. 35 0
      Terminal.Gui/ViewBase/IMouseHeldDown.cs
  35. 111 0
      Terminal.Gui/ViewBase/MouseHeldDown.cs
  36. 26 11
      Terminal.Gui/ViewBase/View.Mouse.cs
  37. 2 7
      Terminal.Gui/ViewBase/View.cs
  38. 10 10
      Terminal.Gui/Views/Autocomplete/Autocomplete.cd
  39. 1 1
      Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs
  40. 5 0
      Terminal.Gui/Views/Button.cs
  41. 1 1
      Terminal.Gui/Views/CharMap/CharMap.cs
  42. 29 29
      Terminal.Gui/Views/CollectionNavigation/CollectionNavigation.cd
  43. 2 2
      Terminal.Gui/Views/ComboBox.cs
  44. 9 13
      Terminal.Gui/Views/DatePicker.cs
  45. 84 113
      Terminal.Gui/Views/FileDialogs/FileDialog.cd
  46. 4 4
      Terminal.Gui/Views/Menuv1/Menu.cs
  47. 34 34
      Terminal.Gui/Views/Menuv1/MenuBar.cs
  48. 4 4
      Terminal.Gui/Views/ScrollBar/ScrollSlider.cs
  49. 2 2
      Terminal.Gui/Views/Slider/Slider.cs
  50. 6 6
      Terminal.Gui/Views/TextInput/TextField.cs
  51. 6 6
      Terminal.Gui/Views/TextInput/TextView.cs
  52. 2 2
      Terminal.Gui/Views/TileView.cs
  53. 1 1
      Tests/TerminalGuiFluentTesting/GuiTestContext.cs
  54. 27 18
      Tests/UnitTests/Application/ApplicationTests.cs
  55. 79 101
      Tests/UnitTests/Application/MainLoopTests.cs
  56. 31 31
      Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs
  57. 18 19
      Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs
  58. 2 2
      Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
  59. 3 28
      Tests/UnitTests/Input/EscSeqUtilsTests.cs
  60. 1 1
      Tests/UnitTests/View/Adornment/ShadowStyleTests.cs
  61. 71 29
      Tests/UnitTests/View/Mouse/MouseTests.cs
  62. 2 2
      Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs
  63. 3 0
      Tests/UnitTests/Views/SpinnerViewTests.cs
  64. 28 28
      Tests/UnitTests/Views/ToplevelTests.cs
  65. 81 0
      Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs
  66. 70 0
      Tests/UnitTestsParallelizable/Application/SmoothAcceleratingTimeoutTests.cs
  67. 1 2
      Tests/UnitTestsParallelizable/TestSetup.cs
  68. 0 38
      docfx/docs/multitasking.md

+ 0 - 3
Directory.Packages.props

@@ -16,9 +16,6 @@
     <PackageVersion Include="Microsoft.CodeAnalysis" Version="4.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
-
-    <PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.11.0" />
-
     <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[9.0.2,10)" />
     <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6" />
     <PackageVersion Include="System.IO.Abstractions" Version="[22.0.11,23)" />

+ 88 - 0
Examples/UICatalog/Scenarios/Threading.cs

@@ -19,6 +19,16 @@ public class Threading : Scenario
     private ListView _logJob;
     private Action _sync;
 
+    private LogarithmicTimeout _logarithmicTimeout;
+    private NumericUpDown _numberLog;
+    private Button _btnLogarithmic;
+    private object _timeoutObj;
+
+    private SmoothAcceleratingTimeout _smoothTimeout;
+    private NumericUpDown _numberSmooth;
+    private Button _btnSmooth;
+    private object _timeoutObjSmooth;
+
     public override void Main ()
     {
         Application.Init ();
@@ -82,6 +92,35 @@ public class Threading : Scenario
 
         var text = new TextField { X = 1, Y = 3, Width = 100, Text = "Type anything after press the button" };
 
+        _btnLogarithmic = new Button ()
+        {
+            X = 50,
+            Y = 4,
+            Text = "Start Log Counter"
+        };
+        _btnLogarithmic.Accepting += StartStopLogTimeout;
+
+        _numberLog = new NumericUpDown ()
+        {
+            X = Pos.Right (_btnLogarithmic),
+            Y = 4,
+        };
+
+        _btnSmooth = new Button ()
+        {
+            X = Pos.Right (_numberLog),
+            Y = 4,
+            Text = "Start Smooth Counter"
+        };
+        _btnSmooth.Accepting += StartStopSmoothTimeout;
+
+        _numberSmooth = new NumericUpDown ()
+        {
+            X = Pos.Right (_btnSmooth),
+            Y = 4,
+        };
+
+
         var btnAction = new Button { X = 80, Y = 10, Text = "Load Data Action" };
         btnAction.Accepting += (s, e) => _action.Invoke ();
         var btnLambda = new Button { X = 80, Y = 12, Text = "Load Data Lambda" };
@@ -107,6 +146,10 @@ public class Threading : Scenario
                  _btnActionCancel,
                  _logJob,
                  text,
+                 _btnLogarithmic,
+                 _numberLog,
+                 _btnSmooth,
+                 _numberSmooth,
                  btnAction,
                  btnLambda,
                  btnHandler,
@@ -129,6 +172,51 @@ public class Threading : Scenario
         Application.Shutdown ();
     }
 
+    private bool LogTimeout ()
+    {
+        _numberLog.Value++;
+        _logarithmicTimeout.AdvanceStage ();
+        return true;
+    }
+    private bool SmoothTimeout ()
+    {
+        _numberSmooth.Value++;
+        _smoothTimeout.AdvanceStage ();
+        return true;
+    }
+
+    private void StartStopLogTimeout (object sender, CommandEventArgs e)
+    {
+        if (_timeoutObj != null)
+        {
+            _btnLogarithmic.Text = "Start Log Counter";
+            Application.TimedEvents.Remove (_timeoutObj);
+            _timeoutObj = null;
+        }
+        else
+        {
+            _btnLogarithmic.Text = "Stop Log Counter";
+            _logarithmicTimeout = new LogarithmicTimeout (TimeSpan.FromMilliseconds (500), LogTimeout);
+            _timeoutObj = Application.TimedEvents.Add (_logarithmicTimeout);
+        }
+    }
+
+    private void StartStopSmoothTimeout (object sender, CommandEventArgs e)
+    {
+        if (_timeoutObjSmooth != null)
+        {
+            _btnSmooth.Text = "Start Smooth Counter";
+            Application.TimedEvents.Remove (_timeoutObjSmooth);
+            _timeoutObjSmooth = null;
+        }
+        else
+        {
+            _btnSmooth.Text = "Stop Smooth Counter";
+            _smoothTimeout = new SmoothAcceleratingTimeout (TimeSpan.FromMilliseconds (500), TimeSpan.FromMilliseconds (50), 0.5, SmoothTimeout);
+            _timeoutObjSmooth = Application.TimedEvents.Add (_smoothTimeout);
+        }
+    }
+
     private async void CallLoadItemsAsync ()
     {
         _cancellationTokenSource = new CancellationTokenSource ();

+ 13 - 133
Terminal.Gui/App/Application.Mouse.cs

@@ -19,122 +19,16 @@ public static partial class Application // Mouse handling
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static bool IsMouseDisabled { get; set; }
 
-    /// <summary>Gets <see cref="View"/> that has registered to get continuous mouse button pressed events.</summary>
-    public static View? WantContinuousButtonPressedView { get; internal set; }
-
     /// <summary>
-    ///     Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be routed to
-    ///     this view until the view calls <see cref="UngrabMouse"/> or the mouse is released.
+    /// Static reference to the current <see cref="IApplication"/> <see cref="IMouseGrabHandler"/>.
     /// </summary>
-    public static View? MouseGrabView { get; private set; }
-
-    /// <summary>Invoked when a view wants to grab the mouse; can be canceled.</summary>
-    public static event EventHandler<GrabMouseEventArgs>? GrabbingMouse;
-
-    /// <summary>Invoked when a view wants un-grab the mouse; can be canceled.</summary>
-    public static event EventHandler<GrabMouseEventArgs>? UnGrabbingMouse;
-
-    /// <summary>Invoked after a view has grabbed the mouse.</summary>
-    public static event EventHandler<ViewEventArgs>? GrabbedMouse;
-
-    /// <summary>Invoked after a view has un-grabbed the mouse.</summary>
-    public static event EventHandler<ViewEventArgs>? UnGrabbedMouse;
-
-    /// <summary>
-    ///     Grabs the mouse, forcing all mouse events to be routed to the specified view until <see cref="UngrabMouse"/>
-    ///     is called.
-    /// </summary>
-    /// <param name="view">View that will receive all mouse events until <see cref="UngrabMouse"/> is invoked.</param>
-    public static void GrabMouse (View? view)
+    public static IMouseGrabHandler MouseGrabHandler
     {
-        if (view is null || RaiseGrabbingMouseEvent (view))
-        {
-            return;
-        }
-
-        RaiseGrabbedMouseEvent (view);
-
-        if (Initialized)
-        {
-            // MouseGrabView is a static; only set if the application is initialized.
-            MouseGrabView = view;
-        }
+        get => ApplicationImpl.Instance.MouseGrabHandler;
+        set => ApplicationImpl.Instance.MouseGrabHandler = value ??
+                                                           throw new ArgumentNullException(nameof(value));
     }
 
-    /// <summary>Releases the mouse grab, so mouse events will be routed to the view on which the mouse is.</summary>
-    public static void UngrabMouse ()
-    {
-        if (MouseGrabView is null)
-        {
-            return;
-        }
-
-#if DEBUG_IDISPOSABLE
-        if (View.EnableDebugIDisposableAsserts)
-        {
-            ObjectDisposedException.ThrowIf (MouseGrabView.WasDisposed, MouseGrabView);
-        }
-#endif
-
-        if (!RaiseUnGrabbingMouseEvent (MouseGrabView))
-        {
-            View view = MouseGrabView;
-            MouseGrabView = null;
-            RaiseUnGrabbedMouseEvent (view);
-        }
-    }
-
-    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
-    private static bool RaiseGrabbingMouseEvent (View? view)
-    {
-        if (view is null)
-        {
-            return false;
-        }
-
-        var evArgs = new GrabMouseEventArgs (view);
-        GrabbingMouse?.Invoke (view, evArgs);
-
-        return evArgs.Cancel;
-    }
-
-    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
-    private static bool RaiseUnGrabbingMouseEvent (View? view)
-    {
-        if (view is null)
-        {
-            return false;
-        }
-
-        var evArgs = new GrabMouseEventArgs (view);
-        UnGrabbingMouse?.Invoke (view, evArgs);
-
-        return evArgs.Cancel;
-    }
-
-    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
-    private static void RaiseGrabbedMouseEvent (View? view)
-    {
-        if (view is null)
-        {
-            return;
-        }
-
-        GrabbedMouse?.Invoke (view, new (view));
-    }
-
-    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
-    private static void RaiseUnGrabbedMouseEvent (View? view)
-    {
-        if (view is null)
-        {
-            return;
-        }
-
-        UnGrabbedMouse?.Invoke (view, new (view));
-    }
-
-
     /// <summary>
     ///     INTERNAL API: Called when a mouse event is raised by the driver. Determines the view under the mouse and
     ///     calls the appropriate View mouse event handlers.
@@ -198,15 +92,6 @@ public static partial class Application // Mouse handling
             return;
         }
 
-        if (Initialized)
-        {
-            WantContinuousButtonPressedView = deepestViewUnderMouse switch
-            {
-                { WantContinuousButtonPressed: true } => deepestViewUnderMouse,
-                _ => null
-            };
-        }
-
         // May be null before the prior condition or the condition may set it as null.
         // So, the checking must be outside the prior condition.
         if (deepestViewUnderMouse is null)
@@ -258,12 +143,7 @@ public static partial class Application // Mouse handling
 
         RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse);
 
-        if (Initialized)
-        {
-            WantContinuousButtonPressedView = deepestViewUnderMouse.WantContinuousButtonPressed ? deepestViewUnderMouse : null;
-        }
-
-        while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabView is not { })
+        while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabHandler.MouseGrabView is not { })
         {
             if (deepestViewUnderMouse is Adornment adornmentView)
             {
@@ -315,35 +195,35 @@ public static partial class Application // Mouse handling
 
     internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent)
     {
-        if (MouseGrabView is { })
+        if (MouseGrabHandler.MouseGrabView is { })
         {
 #if DEBUG_IDISPOSABLE
-            if (View.EnableDebugIDisposableAsserts && MouseGrabView.WasDisposed)
+            if (View.EnableDebugIDisposableAsserts && MouseGrabHandler.MouseGrabView.WasDisposed)
             {
-                throw new ObjectDisposedException (MouseGrabView.GetType ().FullName);
+                throw new ObjectDisposedException (MouseGrabHandler.MouseGrabView.GetType ().FullName);
             }
 #endif
 
             // If the mouse is grabbed, send the event to the view that grabbed it.
             // The coordinates are relative to the Bounds of the view that grabbed the mouse.
-            Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);
+            Point frameLoc = MouseGrabHandler.MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);
 
             var viewRelativeMouseEvent = new MouseEventArgs
             {
                 Position = frameLoc,
                 Flags = mouseEvent.Flags,
                 ScreenPosition = mouseEvent.ScreenPosition,
-                View = deepestViewUnderMouse ?? MouseGrabView
+                View = deepestViewUnderMouse ?? MouseGrabHandler.MouseGrabView
             };
 
             //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
-            if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
+            if (MouseGrabHandler.MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
             {
                 return true;
             }
 
             // ReSharper disable once ConditionIsAlwaysTrueOrFalse
-            if (MouseGrabView is null && deepestViewUnderMouse is Adornment)
+            if (MouseGrabHandler.MouseGrabView is null && deepestViewUnderMouse is Adornment)
             {
                 // The view that grabbed the mouse has been disposed
                 return true;

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

@@ -89,10 +89,9 @@ public static partial class Application // Run (Begin, Run, End, Stop)
         //#endif
 
         // Ensure the mouse is ungrabbed.
-        if (MouseGrabView is { })
+        if (MouseGrabHandler.MouseGrabView is { })
         {
-            UngrabMouse ();
-            MouseGrabView = null;
+            MouseGrabHandler.UngrabMouse ();
         }
 
         var rs = new RunState (toplevel);
@@ -366,7 +365,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     ///         Alternatively, to have a program control the main loop and process events manually, call
     ///         <see cref="Begin(Toplevel)"/> to set things up manually and then repeatedly call
     ///         <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
-    ///         <see cref="RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
+    ///         <see cref="RunLoop(RunState)"/> method will only process any pending events, timers handlers and then
     ///         return control immediately.
     ///     </para>
     ///     <para>

+ 13 - 4
Terminal.Gui/App/Application.Screen.cs

@@ -4,6 +4,7 @@ namespace Terminal.Gui.App;
 
 public static partial class Application // Screen related stuff
 {
+    private static readonly object _lockScreen = new ();
     private static Rectangle? _screen;
 
     /// <summary>
@@ -18,11 +19,15 @@ public static partial class Application // Screen related stuff
     {
         get
         {
-            if (_screen == null)
+            lock (_lockScreen)
             {
-                _screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
+                if (_screen == null)
+                {
+                    _screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
+                }
+
+                return _screen.Value;
             }
-            return _screen.Value;
         }
         set
         {
@@ -30,7 +35,11 @@ public static partial class Application // Screen related stuff
             {
                 throw new NotImplementedException ($"Screen locations other than 0, 0 are not yet supported");
             }
-            _screen = value;
+
+            lock (_lockScreen)
+            {
+                _screen = value;
+            }
         }
     }
 

+ 65 - 39
Terminal.Gui/App/Application.cd

@@ -1,90 +1,116 @@
 <?xml version="1.0" encoding="utf-8"?>
 <ClassDiagram MajorVersion="1" MinorVersion="1">
-  <Class Name="Terminal.Gui.Application">
+  <Class Name="Terminal.Gui.App.Application">
     <Position X="2.25" Y="1.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>hEI4FAgAqARIspQfBQo0gTGiACNL0AICESJKoggBSg8=</HashCode>
-      <FileName>Application\Application.cs</FileName>
+      <HashCode>gEK4FIgQOAQIuhQeBwoUgSCgAAJL0AACESIKoAiBWw8=</HashCode>
+      <FileName>App\Application.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.ApplicationNavigation" Collapsed="true">
-    <Position X="13.75" Y="1.75" Width="2" />
+  <Class Name="Terminal.Gui.App.ApplicationNavigation" Collapsed="true">
+    <Position X="14.75" Y="2.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AABAAAAAAABCAAAAAAAAAAAAAAAAIgIAAAAAAAAAAAA=</HashCode>
-      <FileName>Application\ApplicationNavigation.cs</FileName>
+      <FileName>App\ApplicationNavigation.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.IterationEventArgs" Collapsed="true">
-    <Position X="16" Y="2" Width="2" />
+  <Class Name="Terminal.Gui.App.IterationEventArgs" Collapsed="true">
+    <Position X="17" Y="3" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>Application\IterationEventArgs.cs</FileName>
+      <FileName>App\IterationEventArgs.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.MainLoop" Collapsed="true" BaseTypeListCollapsed="true">
-    <Position X="10.25" Y="2.75" Width="1.5" />
+  <Class Name="Terminal.Gui.App.MainLoop" Collapsed="true" BaseTypeListCollapsed="true">
+    <Position X="11.25" Y="3.75" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>CAAAIAAAASAAAQAQAAAAAIBADQAAEAAYIgIIwAAAAAI=</HashCode>
-      <FileName>Application\MainLoop.cs</FileName>
+      <HashCode>AAAAAAAAACAAAAAAAAAAAAAACBAAEAAIIAIAgAAAEAI=</HashCode>
+      <FileName>App\MainLoop.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" Collapsed="true" />
   </Class>
-  <Class Name="Terminal.Gui.MainLoopSyncContext" Collapsed="true">
-    <Position X="12" Y="2.75" Width="2" />
+  <Class Name="Terminal.Gui.App.MainLoopSyncContext" Collapsed="true">
+    <Position X="13" Y="3.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAgAAAAAAAAAAAEAAAAAACAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>Application\MainLoopSyncContext.cs</FileName>
+      <FileName>App\MainLoopSyncContext.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.RunState" Collapsed="true" BaseTypeListCollapsed="true">
-    <Position X="14.25" Y="3" Width="1.5" />
+  <Class Name="Terminal.Gui.App.RunState" Collapsed="true" BaseTypeListCollapsed="true">
+    <Position X="15.25" Y="4" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAACACAgAAAAAAAAAAAAAAAAACQAAAAAAAAAA=</HashCode>
-      <FileName>Application\RunState.cs</FileName>
+      <FileName>App\RunState.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" Collapsed="true" />
   </Class>
-  <Class Name="Terminal.Gui.RunStateEventArgs" Collapsed="true">
-    <Position X="16" Y="3" Width="2" />
+  <Class Name="Terminal.Gui.App.RunStateEventArgs" Collapsed="true">
+    <Position X="17" Y="4" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=</HashCode>
-      <FileName>Application\RunStateEventArgs.cs</FileName>
+      <FileName>App\RunStateEventArgs.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.Timeout" Collapsed="true">
-    <Position X="10.25" Y="3.75" Width="1.5" />
+  <Class Name="Terminal.Gui.App.Timeout" Collapsed="true">
+    <Position X="11.25" Y="4.75" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAQAA=</HashCode>
-      <FileName>Application\Timeout.cs</FileName>
+      <FileName>App\Timeout.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.TimeoutEventArgs" Collapsed="true">
-    <Position X="12" Y="3.75" Width="2" />
+  <Class Name="Terminal.Gui.App.TimeoutEventArgs" Collapsed="true">
+    <Position X="13" Y="4.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAACAIAAAAAAAAAAAA=</HashCode>
-      <FileName>Application\TimeoutEventArgs.cs</FileName>
+      <FileName>App\TimeoutEventArgs.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.ApplicationImpl" BaseTypeListCollapsed="true">
-    <Position X="5.75" Y="1.75" Width="1.5" />
+  <Class Name="Terminal.Gui.App.ApplicationImpl" BaseTypeListCollapsed="true">
+    <Position X="4" Y="5" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAQAACAACAAAI=</HashCode>
-      <FileName>Application\ApplicationImpl.cs</FileName>
+      <HashCode>AABgAAAAIAAIAgQUAAAAAQAAAAAAAAAAQAAKgAAAEAI=</HashCode>
+      <FileName>App\ApplicationImpl.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Interface Name="Terminal.Gui.IMainLoopDriver" Collapsed="true">
-    <Position X="12" Y="5" Width="1.5" />
+  <Class Name="Terminal.Gui.App.MouseGrabHandler" Collapsed="true">
+    <Position X="6.25" Y="9.25" Width="2" />
+    <TypeIdentifier>
+      <HashCode>BAAgAAAAgABAAoAAAAAAABAAACEAAAAAAABAAgAAAAA=</HashCode>
+      <FileName>App\MouseGrabHandler.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Interface Name="Terminal.Gui.App.IMainLoopDriver">
+    <Position X="11.25" Y="1.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAACAAAAAQAAAAABAAAAAAAEAAAAAAAAAAAAAA=</HashCode>
-      <FileName>Application\MainLoop.cs</FileName>
+      <FileName>App\MainLoop.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
+  <Interface Name="Terminal.Gui.App.IApplication">
+    <Position X="4" Y="1.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAgAAAAAAAIAgQUAAAAAQAAAAAAAAAAAAAKgAAAEAI=</HashCode>
+      <FileName>App\IApplication.cs</FileName>
+    </TypeIdentifier>
+    <ShowAsAssociation>
+      <Property Name="MouseGrabHandler" />
+      <Property Name="TimedEvents" />
+    </ShowAsAssociation>
+  </Interface>
+  <Interface Name="Terminal.Gui.App.IMouseGrabHandler">
+    <Position X="7" Y="1.5" Width="2" />
+    <TypeIdentifier>
+      <HashCode>BAAgAAAAAAAAAgAAAAAAABAAACEAAAAAAAAAAgAAAAA=</HashCode>
+      <FileName>App\IMouseGrabHandler.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IApplication">
-    <Position X="4" Y="1.75" Width="1.5" />
+  <Interface Name="Terminal.Gui.App.ITimedEvents">
+    <Position X="7" Y="4.5" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAACAAAAAAI=</HashCode>
-      <FileName>Application\IApplication.cs</FileName>
+      <HashCode>BAAAIAAAAQAAAAAQACAAAIBAAQAAAAAAAAAIgAAAAAA=</HashCode>
+      <FileName>App\ITimedEvents.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Font Name="Segoe UI" Size="9" />

+ 10 - 12
Terminal.Gui/App/Application.cs

@@ -42,6 +42,15 @@ public static partial class Application
     /// <summary>Gets all cultures supported by the application without the invariant language.</summary>
     public static List<CultureInfo>? SupportedCultures { get; private set; } = GetSupportedCultures ();
 
+
+    /// <summary>
+    /// <para>
+    /// Handles recurring events. These are invoked on the main UI thread - allowing for
+    /// safe updates to <see cref="View"/> instances.
+    /// </para>
+    /// </summary>
+    public static ITimedEvents? TimedEvents => ApplicationImpl.Instance?.TimedEvents;
+
     /// <summary>
     ///     Gets a string representation of the Application as rendered by <see cref="Driver"/>.
     /// </summary>
@@ -221,7 +230,7 @@ public static partial class Application
         // Run State stuff
         NotifyNewRunState = null;
         NotifyStopRunState = null;
-        MouseGrabView = null;
+        MouseGrabHandler = new MouseGrabHandler ();
         Initialized = false;
 
         // Mouse
@@ -229,12 +238,7 @@ public static partial class Application
         // last mouse pos.
         //_lastMousePosition = null;
         CachedViewsUnderMouse.Clear ();
-        WantContinuousButtonPressedView = null;
         MouseEvent = null;
-        GrabbedMouse = null;
-        UnGrabbingMouse = null;
-        GrabbedMouse = null;
-        UnGrabbedMouse = null;
 
         // Keyboard
         KeyDown = null;
@@ -252,10 +256,4 @@ public static partial class Application
         // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
         SynchronizationContext.SetSynchronizationContext (null);
     }
-
-    /// <summary>
-    ///     Adds specified idle handler function to main iteration processing. The handler function will be called
-    ///     once per iteration of the main loop after other events have been handled.
-    /// </summary>
-    public static void AddIdle (Func<bool> func) { ApplicationImpl.Instance.AddIdle (func); }
 }

+ 28 - 18
Terminal.Gui/App/ApplicationImpl.cs

@@ -18,6 +18,15 @@ public class ApplicationImpl : IApplication
     /// </summary>
     public static IApplication Instance => _lazyInstance.Value;
 
+
+    /// <inheritdoc/>
+    public virtual ITimedEvents? TimedEvents => Application.MainLoop?.TimedEvents;
+
+    /// <summary>
+    /// Handles which <see cref="View"/> (if any) has captured the mouse
+    /// </summary>
+    public IMouseGrabHandler MouseGrabHandler { get; set; } = new MouseGrabHandler ();
+
     /// <summary>
     /// Change the singleton implementation, should not be called except before application
     /// startup. This method lets you provide alternative implementations of core static gateway
@@ -119,7 +128,7 @@ public class ApplicationImpl : IApplication
     ///         Alternatively, to have a program control the main loop and process events manually, call
     ///         <see cref="Application.Begin(Toplevel)"/> to set things up manually and then repeatedly call
     ///         <see cref="Application.RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
-    ///         <see cref="Application.RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
+    ///         <see cref="Application.RunLoop(RunState)"/> method will only process any pending events, timers handlers and then
     ///         return control immediately.
     ///     </para>
     ///     <para>When using <see cref="Run{T}"/> or
@@ -261,7 +270,22 @@ public class ApplicationImpl : IApplication
     /// <inheritdoc />
     public virtual void Invoke (Action action)
     {
-        Application.MainLoop?.AddIdle (
+
+        // If we are already on the main UI thread
+        if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId)
+        {
+            action ();
+            return;
+        }
+
+        if (Application.MainLoop == null)
+        {
+            Logging.Warning ("Ignored Invoke because MainLoop is not initialized yet");
+            return;
+        }
+
+
+        Application.AddTimeout (TimeSpan.Zero,
                            () =>
                            {
                                action ();
@@ -274,20 +298,6 @@ public class ApplicationImpl : IApplication
     /// <inheritdoc />
     public bool IsLegacy { get; protected set; } = true;
 
-    /// <inheritdoc />
-    public virtual void AddIdle (Func<bool> func)
-    {
-        if (Application.MainLoop is null)
-        {
-            throw new NotInitializedException ("Cannot add idle before main loop is initialized");
-        }
-
-        // Yes in this case we cannot go direct via TimedEvents because legacy main loop
-        // has established behaviour to do other stuff too e.g. 'wake up'.
-        Application.MainLoop.AddIdle (func);
-
-    }
-
     /// <inheritdoc />
     public virtual object AddTimeout (TimeSpan time, Func<bool> callback)
     {
@@ -296,13 +306,13 @@ public class ApplicationImpl : IApplication
             throw new NotInitializedException ("Cannot add timeout before main loop is initialized", null);
         }
 
-        return Application.MainLoop.TimedEvents.AddTimeout (time, callback);
+        return Application.MainLoop.TimedEvents.Add (time, callback);
     }
 
     /// <inheritdoc />
     public virtual bool RemoveTimeout (object token)
     {
-        return Application.MainLoop?.TimedEvents.RemoveTimeout (token) ?? false;
+        return Application.MainLoop?.TimedEvents.Remove (token) ?? false;
     }
 
     /// <inheritdoc />

+ 13 - 8
Terminal.Gui/App/IApplication.cs

@@ -9,6 +9,17 @@ namespace Terminal.Gui.App;
 /// </summary>
 public interface IApplication
 {
+    /// <summary>
+    /// Handles recurring events. These are invoked on the main UI thread - allowing for
+    /// safe updates to <see cref="View"/> instances.
+    /// </summary>
+    ITimedEvents? TimedEvents { get; }
+
+    /// <summary>
+    /// Handles grabbing the mouse (only a single <see cref="View"/> can grab the mouse at once).
+    /// </summary>
+    IMouseGrabHandler MouseGrabHandler { get; set; }
+
     /// <summary>Initializes a new instance of <see cref="Terminal.Gui"/> Application.</summary>
     /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
     /// <para>
@@ -106,7 +117,7 @@ public interface IApplication
     ///         Alternatively, to have a program control the main loop and process events manually, call
     ///         <see cref="Application.Begin(Toplevel)"/> to set things up manually and then repeatedly call
     ///         <see cref="Application.RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
-    ///         <see cref="Application.RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
+    ///         <see cref="Application.RunLoop(RunState)"/> method will only process any pending events, timers handlers and then
     ///         return control immediately.
     ///     </para>
     ///     <para>When using <see cref="Run{T}"/> or
@@ -156,13 +167,7 @@ public interface IApplication
     /// is cutting edge.
     /// </summary>
     bool IsLegacy { get; }
-
-    /// <summary>
-    ///     Adds specified idle handler function to main iteration processing. The handler function will be called
-    ///     once per iteration of the main loop after other events have been handled.
-    /// </summary>
-    void AddIdle (Func<bool> func);
-
+    
     /// <summary>Adds a timeout to the application.</summary>
     /// <remarks>
     ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be

+ 0 - 90
Terminal.Gui/App/ITimedEvents.cs

@@ -1,90 +0,0 @@
-#nullable enable
-using System.Collections.ObjectModel;
-
-namespace Terminal.Gui.App;
-
-/// <summary>
-/// Manages timers and idles
-/// </summary>
-public interface ITimedEvents
-{
-    /// <summary>
-    ///     Adds specified idle handler function to main iteration processing. The handler function will be called
-    ///     once per iteration of the main loop after other events have been handled.
-    /// </summary>
-    /// <param name="idleHandler"></param>
-    void AddIdle (Func<bool> idleHandler);
-
-    /// <summary>
-    /// Runs all idle hooks
-    /// </summary>
-    void LockAndRunIdles ();
-
-    /// <summary>
-    /// Runs all timeouts that are due
-    /// </summary>
-    void LockAndRunTimers ();
-
-    /// <summary>
-    ///     Called from <see cref="IMainLoopDriver.EventsPending"/> to check if there are any outstanding timers or idle
-    ///     handlers.
-    /// </summary>
-    /// <param name="waitTimeout">
-    ///     Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if
-    ///     there are no active timers.
-    /// </param>
-    /// <returns><see langword="true"/> if there is a timer or idle handler active.</returns>
-    bool CheckTimersAndIdleHandlers (out int waitTimeout);
-
-    /// <summary>Adds a timeout to the application.</summary>
-    /// <remarks>
-    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
-    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
-    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
-    /// </remarks>
-    object AddTimeout (TimeSpan time, Func<bool> callback);
-
-    /// <summary>Removes a previously scheduled timeout</summary>
-    /// <remarks>The token parameter is the value returned by AddTimeout.</remarks>
-    /// <returns>
-    /// Returns
-    /// <see langword="true"/>
-    /// if the timeout is successfully removed; otherwise,
-    /// <see langword="false"/>
-    /// .
-    /// This method also returns
-    /// <see langword="false"/>
-    /// if the timeout is not found.
-    /// </returns>
-    bool RemoveTimeout (object token);
-
-    /// <summary>
-    /// Returns all currently registered idles. May not include
-    /// actively executing idles.
-    /// </summary>
-    ReadOnlyCollection<Func<bool>> IdleHandlers { get;}
-
-    /// <summary>
-    /// Returns the next planned execution time (key - UTC ticks)
-    /// for each timeout that is not actively executing.
-    /// </summary>
-    SortedList<long, Timeout> Timeouts { get; }
-
-
-    /// <summary>Removes an idle handler added with <see cref="AddIdle(Func{bool})"/> from processing.</summary>
-    /// <returns>
-    /// <see langword="true"/>
-    /// if the idle handler is successfully removed; otherwise,
-    /// <see langword="false"/>
-    /// .
-    /// This method also returns
-    /// <see langword="false"/>
-    /// if the idle handler is not found.</returns>
-    bool RemoveIdle (Func<bool> fnTrue);
-
-    /// <summary>
-    ///     Invoked when a new timeout is added. To be used in the case when
-    ///     <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
-    /// </summary>
-    event EventHandler<TimeoutEventArgs>? TimeoutAdded;
-}

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

@@ -32,7 +32,7 @@ internal interface IMainLoopDriver
     void Wakeup ();
 }
 
-/// <summary>The MainLoop monitors timers and idle handlers.</summary>
+/// <summary>The main event loop of v1 driver based applications.</summary>
 /// <remarks>
 ///     Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this
 ///     on Windows.
@@ -40,7 +40,7 @@ internal interface IMainLoopDriver
 public class MainLoop : IDisposable
 {
     /// <summary>
-    /// Gets the class responsible for handling idles and timeouts
+    /// Gets the class responsible for handling timeouts
     /// </summary>
     public ITimedEvents TimedEvents { get; } = new TimedEvents();
 
@@ -75,32 +75,6 @@ public class MainLoop : IDisposable
         MainLoopDriver = null;
     }
 
-    /// <summary>
-    ///     Adds specified idle handler function to <see cref="MainLoop"/> processing. The handler function will be called
-    ///     once per iteration of the main loop after other events have been handled.
-    /// </summary>
-    /// <remarks>
-    ///     <para>Remove an idle handler by calling <see cref="TimedEvents.RemoveIdle(Func{bool})"/> with the token this method returns.</para>
-    ///     <para>
-    ///         If the <paramref name="idleHandler"/> returns  <see langword="false"/> it will be removed and not called
-    ///         subsequently.
-    ///     </para>
-    /// </remarks>
-    /// <param name="idleHandler">Token that can be used to remove the idle handler with <see cref="TimedEvents.RemoveIdle(Func{bool})"/> .</param>
-    // QUESTION: Why are we re-inventing the event wheel here?
-    // PERF: This is heavy.
-    // CONCURRENCY: Race conditions exist here.
-    // CONCURRENCY: null delegates will hose this.
-    // 
-    internal Func<bool> AddIdle (Func<bool> idleHandler)
-    {
-        TimedEvents.AddIdle (idleHandler);
-
-        MainLoopDriver?.Wakeup ();
-
-        return idleHandler;
-    }
-
 
     /// <summary>Determines whether there are pending events to be processed.</summary>
     /// <remarks>
@@ -127,7 +101,7 @@ public class MainLoop : IDisposable
 
     /// <summary>Runs one iteration of timers and file watches</summary>
     /// <remarks>
-    ///     Use this to process all pending events (timers, idle handlers and file watches).
+    ///     Use this to process all pending events (timers handlers and file watches).
     ///     <code>
     ///     while (main.EventsPending ()) RunIteration ();
     ///   </code>
@@ -138,9 +112,7 @@ public class MainLoop : IDisposable
 
         MainLoopDriver?.Iteration ();
 
-        TimedEvents.LockAndRunTimers ();
-
-        TimedEvents.LockAndRunIdles ();
+        TimedEvents.RunTimers ();
     }
 
     private void RunAnsiScheduler ()

+ 9 - 7
Terminal.Gui/App/MainLoopSyncContext.cs

@@ -10,14 +10,16 @@ internal sealed class MainLoopSyncContext : SynchronizationContext
 
     public override void Post (SendOrPostCallback d, object state)
     {
-        Application.MainLoop?.AddIdle (
-                                       () =>
-                                       {
-                                           d (state);
+        // Queue the task
+        Application.MainLoop?.TimedEvents.Add (TimeSpan.Zero,
+                                                      () =>
+                                                      {
+                                                          d (state);
 
-                                           return false;
-                                       }
-                                      );
+                                                          return false;
+                                                      }
+                                                     );
+        Application.MainLoop?.Wakeup ();
     }
 
     //_mainLoop.Driver.Wakeup ();

+ 87 - 0
Terminal.Gui/App/Mouse/IMouseGrabHandler.cs

@@ -0,0 +1,87 @@
+#nullable enable
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     Defines a contract for tracking which <see cref="View"/> (if any) has 'grabbed' the mouse,
+///     giving it exclusive priority for mouse events such as movement, button presses, and release.
+///     <para>
+///         This is typically used for scenarios like dragging, scrolling, or any interaction where a view
+///         needs to receive all mouse events until the operation completes (e.g., a scrollbar thumb being dragged).
+///     </para>
+///     <para>
+///         Usage pattern:
+///         <list type="number">
+///             <item>
+///                 <description>Call <see cref="GrabMouse"/> to route all mouse events to a specific view.</description>
+///             </item>
+///             <item>
+///                 <description>Call <see cref="UngrabMouse"/> to release the grab and restore normal mouse routing.</description>
+///             </item>
+///             <item>
+///                 <description>
+///                     Listen to <see cref="GrabbingMouse"/>, <see cref="GrabbedMouse"/>, <see cref="UnGrabbingMouse"/>,
+///                     and <see cref="UnGrabbedMouse"/> for grab lifecycle events.
+///                 </description>
+///             </item>
+///         </list>
+///     </para>
+/// </summary>
+public interface IMouseGrabHandler
+{
+    /// <summary>
+    ///     Occurs after a view has grabbed the mouse.
+    ///     <para>
+    ///         This event is raised after the mouse grab operation is complete and the specified view will receive all mouse
+    ///         events.
+    ///     </para>
+    /// </summary>
+    public event EventHandler<ViewEventArgs>? GrabbedMouse;
+
+    /// <summary>
+    ///     Occurs when a view requests to grab the mouse; can be canceled.
+    ///     <para>
+    ///         Handlers can set <c>e.Cancel</c> to <see langword="true"/> to prevent the grab.
+    ///     </para>
+    /// </summary>
+    public event EventHandler<GrabMouseEventArgs>? GrabbingMouse;
+
+    /// <summary>
+    ///     Grabs the mouse, forcing all mouse events to be routed to the specified view until <see cref="UngrabMouse"/> is
+    ///     called.
+    /// </summary>
+    /// <param name="view">
+    ///     The <see cref="View"/> that will receive all mouse events until <see cref="UngrabMouse"/> is invoked.
+    ///     If <see langword="null"/>, the grab is released.
+    /// </param>
+    public void GrabMouse (View? view);
+
+    /// <summary>
+    ///     Gets the view that currently has grabbed the mouse (e.g., for dragging).
+    ///     <para>
+    ///         When this property is not <see langword="null"/>, all mouse events are routed to this view until
+    ///         <see cref="UngrabMouse"/> is called or the mouse is released.
+    ///     </para>
+    /// </summary>
+    public View? MouseGrabView { get; }
+
+    /// <summary>
+    ///     Occurs after a view has released the mouse grab.
+    ///     <para>
+    ///         This event is raised after the mouse grab has been released and normal mouse routing resumes.
+    ///     </para>
+    /// </summary>
+    public event EventHandler<ViewEventArgs>? UnGrabbedMouse;
+
+    /// <summary>
+    ///     Occurs when a view requests to release the mouse grab; can be canceled.
+    ///     <para>
+    ///         Handlers can set <c>e.Cancel</c> to <see langword="true"/> to prevent the ungrab.
+    ///     </para>
+    /// </summary>
+    public event EventHandler<GrabMouseEventArgs>? UnGrabbingMouse;
+
+    /// <summary>
+    ///     Releases the mouse grab, so mouse events will be routed to the view under the mouse pointer.
+    /// </summary>
+    public void UngrabMouse ();
+}

+ 118 - 0
Terminal.Gui/App/Mouse/MouseGrabHandler.cs

@@ -0,0 +1,118 @@
+#nullable enable
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     INTERNAL: Implements <see cref="IMouseGrabHandler"/> to manage which <see cref="View"/> (if any) has 'grabbed' the mouse,
+///     giving it exclusive priority for mouse events such as movement, button presses, and release.
+///     <para>
+///         Used for scenarios like dragging, scrolling, or any interaction where a view needs to receive all mouse events
+///         until the operation completes (e.g., a scrollbar thumb being dragged).
+///     </para>
+///     <para>
+///         See <see cref="IMouseGrabHandler"/> for usage details.
+///     </para>
+/// </summary>
+internal class MouseGrabHandler : IMouseGrabHandler
+{
+    /// <inheritdoc/>
+    public View? MouseGrabView { get; private set; }
+
+    /// <inheritdoc/>
+    public event EventHandler<GrabMouseEventArgs>? GrabbingMouse;
+
+    /// <inheritdoc/>
+    public event EventHandler<GrabMouseEventArgs>? UnGrabbingMouse;
+
+    /// <inheritdoc/>
+    public event EventHandler<ViewEventArgs>? GrabbedMouse;
+
+    /// <inheritdoc/>
+    public event EventHandler<ViewEventArgs>? UnGrabbedMouse;
+
+    /// <inheritdoc/>
+    public void GrabMouse (View? view)
+    {
+        if (view is null || RaiseGrabbingMouseEvent (view))
+        {
+            return;
+        }
+
+        RaiseGrabbedMouseEvent (view);
+
+        // MouseGrabView is a static; only set if the application is initialized.
+        MouseGrabView = view;
+    }
+
+    /// <inheritdoc/>
+    public void UngrabMouse ()
+    {
+        if (MouseGrabView is null)
+        {
+            return;
+        }
+
+#if DEBUG_IDISPOSABLE
+        if (View.EnableDebugIDisposableAsserts)
+        {
+            ObjectDisposedException.ThrowIf (MouseGrabView.WasDisposed, MouseGrabView);
+        }
+#endif
+
+        if (!RaiseUnGrabbingMouseEvent (MouseGrabView))
+        {
+            View view = MouseGrabView;
+            MouseGrabView = null;
+            RaiseUnGrabbedMouseEvent (view);
+        }
+    }
+
+    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
+    private bool RaiseGrabbingMouseEvent (View? view)
+    {
+        if (view is null)
+        {
+            return false;
+        }
+
+        var evArgs = new GrabMouseEventArgs (view);
+        GrabbingMouse?.Invoke (view, evArgs);
+
+        return evArgs.Cancel;
+    }
+
+    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
+    private bool RaiseUnGrabbingMouseEvent (View? view)
+    {
+        if (view is null)
+        {
+            return false;
+        }
+
+        var evArgs = new GrabMouseEventArgs (view);
+        UnGrabbingMouse?.Invoke (view, evArgs);
+
+        return evArgs.Cancel;
+    }
+
+    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
+    private void RaiseGrabbedMouseEvent (View? view)
+    {
+        if (view is null)
+        {
+            return;
+        }
+
+        GrabbedMouse?.Invoke (view, new (view));
+    }
+
+    /// <exception cref="Exception">A delegate callback throws an exception.</exception>
+    private void RaiseUnGrabbedMouseEvent (View? view)
+    {
+        if (view is null)
+        {
+            return;
+        }
+
+        UnGrabbedMouse?.Invoke (view, new (view));
+    }
+}

+ 0 - 18
Terminal.Gui/App/Timeout.cs

@@ -1,18 +0,0 @@
-//
-// MainLoop.cs: IMainLoopDriver and MainLoop for Terminal.Gui
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-
-namespace Terminal.Gui.App;
-
-/// <summary>Provides data for timers running manipulation.</summary>
-public sealed class Timeout
-{
-    /// <summary>The function that will be invoked.</summary>
-    public Func<bool> Callback;
-
-    /// <summary>Time to wait before invoke the callback.</summary>
-    public TimeSpan Span;
-}

+ 64 - 0
Terminal.Gui/App/Timeout/ITimedEvents.cs

@@ -0,0 +1,64 @@
+#nullable enable
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     Manages timers.
+/// </summary>
+public interface ITimedEvents
+{
+    /// <summary>
+    ///     Adds a timeout to the application.
+    /// </summary>
+    /// <remarks>
+    ///     When the specified time passes, the callback will be invoked. If the callback returns <see langword="true"/>, the
+    ///     timeout will be
+    ///     reset, repeating the invocation. If it returns <see langword="false"/>, the timeout will stop and be removed. The
+    ///     returned value is a
+    ///     token that can be used to stop the timeout by calling <see cref="Remove"/>.
+    /// </remarks>
+    object Add (TimeSpan time, Func<bool> callback);
+
+    /// <inheritdoc cref="Add(System.TimeSpan,System.Func{bool})"/>
+    object Add (Timeout timeout);
+
+    /// <summary>
+    ///     Invoked when a new timeout is added. To be used in the case when
+    ///     <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
+    /// </summary>
+    event EventHandler<TimeoutEventArgs>? Added;
+
+    /// <summary>
+    ///     Called from <see cref="IMainLoopDriver.EventsPending"/> to check if there are any outstanding timer handlers.
+    /// </summary>
+    /// <param name="waitTimeout">
+    ///     Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if
+    ///     there are no active timers.
+    /// </param>
+    /// <returns>
+    ///     <see langword="true"/> if there is a timer active; otherwise, <see langword="false"/>.
+    /// </returns>
+    bool CheckTimers (out int waitTimeout);
+
+    /// <summary>
+    ///     Removes a previously scheduled timeout.
+    /// </summary>
+    /// <remarks>
+    ///     The token parameter is the value returned by <see cref="Add(TimeSpan, Func{bool})"/> or <see cref="Add(Timeout)"/>.
+    /// </remarks>
+    /// <returns>
+    ///     <see langword="true"/> if the timeout is successfully removed; otherwise, <see langword="false"/>.
+    ///     This method also returns <see langword="false"/> if the timeout is not found.
+    /// </returns>
+    bool Remove (object token);
+
+    /// <summary>
+    ///     Runs all timeouts that are due.
+    /// </summary>
+    void RunTimers ();
+
+    /// <summary>
+    ///     Returns the next planned execution time (key - UTC ticks)
+    ///     for each timeout that is not actively executing.
+    /// </summary>
+    SortedList<long, Timeout> Timeouts { get; }
+}

+ 38 - 0
Terminal.Gui/App/Timeout/LogarithmicTimeout.cs

@@ -0,0 +1,38 @@
+namespace Terminal.Gui.App;
+
+/// <summary>Implements a logarithmic increasing timeout.</summary>
+public class LogarithmicTimeout : Timeout
+{
+    /// <summary>
+    ///     Creates a new instance where stages are the logarithm multiplied by the
+    ///     <paramref name="baseDelay"/> (starts fast then slows).
+    /// </summary>
+    /// <param name="baseDelay">Multiple for the logarithm</param>
+    /// <param name="callback">Method to invoke</param>
+    public LogarithmicTimeout (TimeSpan baseDelay, Func<bool> callback)
+    {
+        _baseDelay = baseDelay;
+        Callback = callback;
+    }
+
+    private readonly TimeSpan _baseDelay;
+    private int _stage;
+
+    /// <summary>Increments the stage to increase the timeout.</summary>
+    public void AdvanceStage () { _stage++; }
+
+    /// <summary>Resets the stage back to zero.</summary>
+    public void Reset () { _stage = 0; }
+
+    /// <summary>Gets the current calculated Span based on the stage.</summary>
+    public override TimeSpan Span
+    {
+        get
+        {
+            // Calculate logarithmic increase
+            double multiplier = Math.Log (_stage + 1); // ln(stage + 1)
+
+            return TimeSpan.FromMilliseconds (_baseDelay.TotalMilliseconds * multiplier);
+        }
+    }
+}

+ 53 - 0
Terminal.Gui/App/Timeout/SmoothAcceleratingTimeout.cs

@@ -0,0 +1,53 @@
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     Timeout which accelerates slowly at first then fast up to a maximum speed.
+///     Use <see cref="AdvanceStage"/> to increment the stage of the timer (e.g. in
+///     your timer callback code).
+/// </summary>
+public class SmoothAcceleratingTimeout : Timeout
+{
+    /// <summary>
+    ///     Creates a new instance of the smooth acceleration timeout.
+    /// </summary>
+    /// <param name="initialDelay">Delay before first tick, the longest it will ever take</param>
+    /// <param name="minDelay">The fastest the timer can get no matter how long it runs</param>
+    /// <param name="decayFactor">Controls how fast the timer accelerates</param>
+    /// <param name="callback">Method to call when timer ticks</param>
+    public SmoothAcceleratingTimeout (TimeSpan initialDelay, TimeSpan minDelay, double decayFactor, Func<bool> callback)
+    {
+        _initialDelay = initialDelay;
+        _minDelay = minDelay;
+        _decayFactor = decayFactor;
+        Callback = callback;
+    }
+
+    private readonly TimeSpan _initialDelay;
+    private readonly TimeSpan _minDelay;
+    private readonly double _decayFactor;
+    private int _stage;
+
+    /// <summary>
+    ///     Advances the timer stage, this should be called from your timer callback or whenever
+    ///     you want to advance the speed.
+    /// </summary>
+    public void AdvanceStage () { _stage++; }
+
+    /// <summary>
+    ///     Resets the timer to original speed.
+    /// </summary>
+    public void Reset () { _stage = 0; }
+
+    /// <inheritdoc/>
+    public override TimeSpan Span
+    {
+        get
+        {
+            double initialMs = _initialDelay.TotalMilliseconds;
+            double minMs = _minDelay.TotalMilliseconds;
+            double delayMs = minMs + (initialMs - minMs) * Math.Pow (_decayFactor, _stage);
+
+            return TimeSpan.FromMilliseconds (delayMs);
+        }
+    }
+}

+ 105 - 169
Terminal.Gui/App/TimedEvents.cs → Terminal.Gui/App/Timeout/TimedEvents.cs

@@ -1,137 +1,163 @@
 #nullable enable
-using System.Collections.ObjectModel;
-
 namespace Terminal.Gui.App;
 
 /// <summary>
-/// Handles timeouts and idles
+///     Manages scheduled timeouts (timed callbacks) for the application.
+///     <para>
+///         Allows scheduling of callbacks to be invoked after a specified delay, with optional repetition.
+///         Timeouts are stored in a sorted list by their scheduled execution time (UTC ticks).
+///         Thread-safe for concurrent access.
+///     </para>
+///     <para>
+///         Typical usage:
+///         <list type="number">
+///             <item>
+///                 <description>Call <see cref="Add(TimeSpan, Func{bool})"/> to schedule a callback.</description>
+///             </item>
+///             <item>
+///                 <description>
+///                     Call <see cref="RunTimers"/> periodically (e.g., from the main loop) to execute due
+///                     callbacks.
+///                 </description>
+///             </item>
+///             <item>
+///                 <description>Call <see cref="Remove"/> to cancel a scheduled timeout.</description>
+///             </item>
+///         </list>
+///     </para>
 /// </summary>
 public class TimedEvents : ITimedEvents
 {
-    internal List<Func<bool>> _idleHandlers = new ();
     internal SortedList<long, Timeout> _timeouts = new ();
-
-    /// <summary>The idle handlers and lock that must be held while manipulating them</summary>
-    private readonly object _idleHandlersLock = new ();
-
     private readonly object _timeoutsLockToken = new ();
 
-
-    /// <summary>Gets a copy of the list of all idle handlers.</summary>
-    public ReadOnlyCollection<Func<bool>> IdleHandlers
-    {
-        get
-        {
-            lock (_idleHandlersLock)
-            {
-                return new List<Func<bool>> (_idleHandlers).AsReadOnly ();
-            }
-        }
-    }
-
     /// <summary>
     ///     Gets the list of all timeouts sorted by the <see cref="TimeSpan"/> time ticks. A shorter limit time can be
     ///     added at the end, but it will be called before an earlier addition that has a longer limit time.
     /// </summary>
     public SortedList<long, Timeout> Timeouts => _timeouts;
 
-    /// <inheritdoc />
-    public void AddIdle (Func<bool> idleHandler)
-    {
-        lock (_idleHandlersLock)
-        {
-            _idleHandlers.Add (idleHandler);
-        }
-    }
-
     /// <inheritdoc/>
-    public event EventHandler<TimeoutEventArgs>? TimeoutAdded;
-
+    public event EventHandler<TimeoutEventArgs>? Added;
 
-    private void AddTimeout (TimeSpan time, Timeout timeout)
+    /// <inheritdoc/>
+    public void RunTimers ()
     {
         lock (_timeoutsLockToken)
         {
-            long k = (DateTime.UtcNow + time).Ticks;
-            _timeouts.Add (NudgeToUniqueKey (k), timeout);
-            TimeoutAdded?.Invoke (this, new TimeoutEventArgs (timeout, k));
+            if (_timeouts.Count > 0)
+            {
+                RunTimersImpl ();
+            }
         }
     }
 
-    /// <summary>
-    ///     Finds the closest number to <paramref name="k"/> that is not present in <see cref="_timeouts"/>
-    ///     (incrementally).
-    /// </summary>
-    /// <param name="k"></param>
-    /// <returns></returns>
-    private long NudgeToUniqueKey (long k)
+    /// <inheritdoc/>
+    public bool Remove (object token)
     {
         lock (_timeoutsLockToken)
         {
-            while (_timeouts.ContainsKey (k))
+            int idx = _timeouts.IndexOfValue ((token as Timeout)!);
+
+            if (idx == -1)
             {
-                k++;
+                return false;
             }
+
+            _timeouts.RemoveAt (idx);
         }
 
-        return k;
+        return true;
     }
 
+    /// <inheritdoc/>
+    public object Add (TimeSpan time, Func<bool> callback)
+    {
+        ArgumentNullException.ThrowIfNull (callback);
+
+        var timeout = new Timeout { Span = time, Callback = callback };
+        AddTimeout (time, timeout);
 
-    // PERF: This is heavier than it looks.
-    // CONCURRENCY: Potential deadlock city here.
-    // CONCURRENCY: Multiple concurrency pitfalls on the delegates themselves.
-    // INTENT: It looks like the general architecture here is trying to be a form of publisher/consumer pattern.
-    private void RunIdle ()
+        return timeout;
+    }
+
+    /// <inheritdoc/>
+    public object Add (Timeout timeout)
     {
-        Func<bool> [] iterate;
-        lock (_idleHandlersLock)
-        {
-            iterate = _idleHandlers.ToArray ();
-            _idleHandlers = new List<Func<bool>> ();
-        }
+        AddTimeout (timeout.Span, timeout);
 
-        foreach (Func<bool> idle in iterate)
+        return timeout;
+    }
+
+    /// <inheritdoc/>
+    public bool CheckTimers (out int waitTimeout)
+    {
+        long now = DateTime.UtcNow.Ticks;
+
+        waitTimeout = 0;
+
+        lock (_timeoutsLockToken)
         {
-            if (idle ())
+            if (_timeouts.Count > 0)
             {
-                lock (_idleHandlersLock)
+                waitTimeout = (int)((_timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
+
+                if (waitTimeout < 0)
                 {
-                    _idleHandlers.Add (idle);
+                    // This avoids 'poll' waiting infinitely if 'waitTimeout < 0' until some action is detected
+                    // This can occur after IMainLoopDriver.Wakeup is executed where the pollTimeout is less than 0
+                    // and no event occurred in elapsed time when the 'poll' is start running again.
+                    waitTimeout = 0;
                 }
+
+                return true;
             }
+
+            // ManualResetEventSlim.Wait, which is called by IMainLoopDriver.EventsPending, will wait indefinitely if
+            // the timeout is -1.
+            waitTimeout = -1;
         }
+
+        return false;
     }
 
-    /// <inheritdoc/>
-    public void LockAndRunTimers ()
+    private void AddTimeout (TimeSpan time, Timeout timeout)
     {
         lock (_timeoutsLockToken)
         {
-            if (_timeouts.Count > 0)
+            long k = (DateTime.UtcNow + time).Ticks;
+
+            // if user wants to run as soon as possible set timer such that it expires right away (no race conditions)
+            if (time == TimeSpan.Zero)
             {
-                RunTimers ();
+                k -= 100;
             }
-        }
 
+            _timeouts.Add (NudgeToUniqueKey (k), timeout);
+            Added?.Invoke (this, new (timeout, k));
+        }
     }
 
-    /// <inheritdoc/>
-    public void LockAndRunIdles ()
+    /// <summary>
+    ///     Finds the closest number to <paramref name="k"/> that is not present in <see cref="_timeouts"/>
+    ///     (incrementally).
+    /// </summary>
+    /// <param name="k"></param>
+    /// <returns></returns>
+    private long NudgeToUniqueKey (long k)
     {
-        bool runIdle;
-
-        lock (_idleHandlersLock)
+        lock (_timeoutsLockToken)
         {
-            runIdle = _idleHandlers.Count > 0;
+            while (_timeouts.ContainsKey (k))
+            {
+                k++;
+            }
         }
 
-        if (runIdle)
-        {
-            RunIdle ();
-        }
+        return k;
     }
-    private void RunTimers ()
+
+    private void RunTimersImpl ()
     {
         long now = DateTime.UtcNow.Ticks;
         SortedList<long, Timeout> copy;
@@ -143,7 +169,7 @@ public class TimedEvents : ITimedEvents
         lock (_timeoutsLockToken)
         {
             copy = _timeouts;
-            _timeouts = new SortedList<long, Timeout> ();
+            _timeouts = new ();
         }
 
         foreach ((long k, Timeout timeout) in copy)
@@ -164,94 +190,4 @@ public class TimedEvents : ITimedEvents
             }
         }
     }
-
-    /// <inheritdoc/>
-    public bool RemoveIdle (Func<bool> token)
-    {
-        lock (_idleHandlersLock)
-        {
-            return _idleHandlers.Remove (token);
-        }
-    }
-
-    /// <summary>Removes a previously scheduled timeout</summary>
-    /// <remarks>The token parameter is the value returned by AddTimeout.</remarks>
-    /// Returns
-    /// <see langword="true"/>
-    /// if the timeout is successfully removed; otherwise,
-    /// <see langword="false"/>
-    /// .
-    /// This method also returns
-    /// <see langword="false"/>
-    /// if the timeout is not found.
-    public bool RemoveTimeout (object token)
-    {
-        lock (_timeoutsLockToken)
-        {
-            int idx = _timeouts.IndexOfValue ((token as Timeout)!);
-
-            if (idx == -1)
-            {
-                return false;
-            }
-
-            _timeouts.RemoveAt (idx);
-        }
-
-        return true;
-    }
-
-
-    /// <summary>Adds a timeout to the <see cref="MainLoop"/>.</summary>
-    /// <remarks>
-    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
-    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
-    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
-    /// </remarks>
-    public object AddTimeout (TimeSpan time, Func<bool> callback)
-    {
-        ArgumentNullException.ThrowIfNull (callback);
-
-        var timeout = new Timeout { Span = time, Callback = callback };
-        AddTimeout (time, timeout);
-
-        return timeout;
-    }
-
-    /// <inheritdoc/>
-    public bool CheckTimersAndIdleHandlers (out int waitTimeout)
-    {
-        long now = DateTime.UtcNow.Ticks;
-
-        waitTimeout = 0;
-
-        lock (_timeoutsLockToken)
-        {
-            if (_timeouts.Count > 0)
-            {
-                waitTimeout = (int)((_timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
-
-                if (waitTimeout < 0)
-                {
-                    // This avoids 'poll' waiting infinitely if 'waitTimeout < 0' until some action is detected
-                    // This can occur after IMainLoopDriver.Wakeup is executed where the pollTimeout is less than 0
-                    // and no event occurred in elapsed time when the 'poll' is start running again.
-                    waitTimeout = 0;
-                }
-
-                return true;
-            }
-
-            // ManualResetEventSlim.Wait, which is called by IMainLoopDriver.EventsPending, will wait indefinitely if
-            // the timeout is -1.
-            waitTimeout = -1;
-        }
-
-        // There are no timers set, check if there are any idle handlers
-
-        lock (_idleHandlersLock)
-        {
-            return _idleHandlers.Count > 0;
-        }
-    }
-}
+}

+ 33 - 0
Terminal.Gui/App/Timeout/Timeout.cs

@@ -0,0 +1,33 @@
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     Represents a scheduled timeout for use with timer management APIs.
+///     <para>
+///         Encapsulates a callback function to be invoked after a specified time interval. The callback can optionally
+///         indicate whether the timeout should repeat.
+///     </para>
+///     <para>
+///         Used by <see cref="ITimedEvents"/> and related timer systems to manage timed operations in the application.
+///     </para>
+/// </summary>
+public class Timeout
+{
+    /// <summary>
+    ///     Gets or sets the function to invoke when the timeout expires.
+    /// </summary>
+    /// <value>
+    ///     A <see cref="Func{Boolean}"/> delegate. If the callback returns <see langword="true"/>, the timeout will be
+    ///     rescheduled and invoked again after the same interval.
+    ///     If the callback returns <see langword="false"/>, the timeout will be removed and not invoked again.
+    /// </value>
+    public Func<bool> Callback { get; set; }
+
+    /// <summary>
+    ///     Gets or sets the time interval to wait before invoking the <see cref="Callback"/>.
+    /// </summary>
+    /// <value>
+    ///     A <see cref="TimeSpan"/> representing the delay before the callback is invoked. If the timeout is rescheduled
+    ///     (i.e., <see cref="Callback"/> returns <see langword="true"/>), this interval is used again.
+    /// </value>
+    public virtual TimeSpan Span { get; set; }
+}

+ 1 - 1
Terminal.Gui/App/TimeoutEventArgs.cs → Terminal.Gui/App/Timeout/TimeoutEventArgs.cs

@@ -1,6 +1,6 @@
 namespace Terminal.Gui.App;
 
-/// <summary><see cref="EventArgs"/> for timeout events (e.g. <see cref="TimedEvents.TimeoutAdded"/>)</summary>
+/// <summary><see cref="EventArgs"/> for timeout events (e.g. <see cref="TimedEvents.Added"/>)</summary>
 public class TimeoutEventArgs : EventArgs
 {
     /// <summary>Creates a new instance of the <see cref="TimeoutEventArgs"/> class.</summary>

+ 1 - 1
Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs

@@ -104,7 +104,7 @@ internal class UnixMainLoop : IMainLoopDriver
 
         UpdatePollMap ();
 
-        bool checkTimersResult = _mainLoop!.TimedEvents.CheckTimersAndIdleHandlers (out int pollTimeout);
+        bool checkTimersResult = _mainLoop!.TimedEvents.CheckTimers (out int pollTimeout);
 
         int n = poll (_pollMap!, (uint)_pollMap!.Length, pollTimeout);
 

+ 4 - 41
Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs

@@ -900,22 +900,8 @@ public static class EscSeqUtils
 
             _point = pos;
 
-            if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0)
-            {
-                Application.MainLoop?.AddIdle (
-                                              () =>
-                                              {
-                                                  // INTENT: What's this trying to do?
-                                                  // The task itself is not awaited.
-                                                  Task.Run (
-                                                            async () => await ProcessContinuousButtonPressedAsync (
-                                                                         buttonState,
-                                                                         continuousButtonPressedHandler));
 
-                                                  return false;
-                                              });
-            }
-            else if (mouseFlags [0].HasFlag (MouseFlags.ReportMousePosition))
+            if (mouseFlags [0].HasFlag (MouseFlags.ReportMousePosition))
             {
                 _point = pos;
 
@@ -945,7 +931,7 @@ public static class EscSeqUtils
             _isButtonClicked = false;
             _isButtonDoubleClicked = true;
 
-            Application.MainLoop?.AddIdle (
+            Application.MainLoop?.TimedEvents.Add (TimeSpan.Zero,
                                           () =>
                                           {
                                               Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
@@ -959,7 +945,7 @@ public static class EscSeqUtils
         //	lastMouseButtonReleased = null;
         //	isButtonReleased = false;
         //	isButtonClicked = true;
-        //	Application.MainLoop.AddIdle (() => {
+        //	Application.MainLoop.AddTimeout (() => {
         //		Task.Run (async () => await ProcessButtonClickedAsync ());
         //		return false;
         //	});
@@ -984,7 +970,7 @@ public static class EscSeqUtils
                 mouseFlags.Add (GetButtonClicked (buttonState));
                 _isButtonClicked = true;
 
-                Application.MainLoop?.AddIdle (
+                Application.MainLoop?.TimedEvents.Add (TimeSpan.Zero,
                                               () =>
                                               {
                                                   Task.Run (async () => await ProcessButtonClickedAsync ());
@@ -1498,29 +1484,6 @@ public static class EscSeqUtils
         _isButtonDoubleClicked = false;
     }
 
-    private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action<MouseFlags, Point> continuousButtonPressedHandler)
-    {
-        // PERF: Pause and poll in a hot loop.
-        // This should be replaced with event dispatch and a synchronization primitive such as AutoResetEvent.
-        // Will make a massive difference in responsiveness.
-        while (_isButtonPressed)
-        {
-            await Task.Delay (100);
-
-            View view = Application.WantContinuousButtonPressedView;
-
-            if (view is null)
-            {
-                break;
-            }
-
-            if (_isButtonPressed && _lastMouseButtonPressed is { } && (mouseFlag & MouseFlags.ReportMousePosition) == 0)
-            {
-                Application.Invoke (() => continuousButtonPressedHandler (mouseFlag, _point ?? Point.Empty));
-            }
-        }
-    }
-
     private static MouseFlags SetControlKeyStates (MouseFlags buttonState, MouseFlags mouseFlag)
     {
         if ((buttonState & MouseFlags.ButtonCtrl) != 0 && (mouseFlag & MouseFlags.ButtonCtrl) == 0)

+ 2 - 2
Terminal.Gui/Drivers/NetDriver/NetMainLoop.cs

@@ -57,7 +57,7 @@ internal class NetMainLoop : IMainLoopDriver
 
         _waitForProbe.Set ();
 
-        if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout))
+        if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimers (out int waitTimeout))
         {
             return true;
         }
@@ -84,7 +84,7 @@ internal class NetMainLoop : IMainLoopDriver
 
         if (!_eventReadyTokenSource.IsCancellationRequested)
         {
-            return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out _);
+            return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimers (out _);
         }
 
         // If cancellation was requested then always return true

+ 13 - 13
Terminal.Gui/Drivers/V2/ApplicationV2.cs

@@ -21,6 +21,9 @@ public class ApplicationV2 : ApplicationImpl
 
     private readonly ITimedEvents _timedEvents = new TimedEvents ();
 
+    /// <inheritdoc/>
+    public override ITimedEvents TimedEvents => _timedEvents;
+
     /// <summary>
     ///     Creates anew instance of the Application backend. The provided
     ///     factory methods will be used on Init calls to get things booted.
@@ -225,7 +228,14 @@ public class ApplicationV2 : ApplicationImpl
     /// <inheritdoc/>
     public override void Invoke (Action action)
     {
-        _timedEvents.AddIdle (
+        // If we are already on the main UI thread
+        if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId)
+        {
+            action ();
+            return;
+        }
+
+        _timedEvents.Add (TimeSpan.Zero,
                               () =>
                               {
                                   action ();
@@ -236,20 +246,10 @@ public class ApplicationV2 : ApplicationImpl
     }
 
     /// <inheritdoc/>
-    public override void AddIdle (Func<bool> func) { _timedEvents.AddIdle (func); }
-
-    /// <summary>
-    ///     Removes an idle function added by <see cref="AddIdle"/>
-    /// </summary>
-    /// <param name="fnTrue">Function to remove</param>
-    /// <returns>True if it was found and removed</returns>
-    public bool RemoveIdle (Func<bool> fnTrue) { return _timedEvents.RemoveIdle (fnTrue); }
-
-    /// <inheritdoc/>
-    public override object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.AddTimeout (time, callback); }
+    public override object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.Add (time, callback); }
 
     /// <inheritdoc/>
-    public override bool RemoveTimeout (object token) { return _timedEvents.RemoveTimeout (token); }
+    public override bool RemoveTimeout (object token) { return _timedEvents.Remove (token); }
 
     /// <inheritdoc />
     public override void LayoutAndDraw (bool forceDraw)

+ 2 - 2
Terminal.Gui/Drivers/V2/IMainLoop.cs

@@ -6,11 +6,11 @@ namespace Terminal.Gui.Drivers;
 /// <summary>
 ///     Interface for main loop that runs the core Terminal.Gui UI loop.
 /// </summary>
-/// <typeparam name="T"></typeparam>
+/// <typeparam name="T">Type of raw input events processed by the loop e.g. <see cref="ConsoleKeyInfo"/></typeparam>
 public interface IMainLoop<T> : IDisposable
 {
     /// <summary>
-    ///     Gets the class responsible for servicing user timeouts and idles
+    ///     Gets the class responsible for servicing user timeouts
     /// </summary>
     public ITimedEvents TimedEvents { get; }
 

+ 1 - 3
Terminal.Gui/Drivers/V2/MainLoop.cs

@@ -143,9 +143,7 @@ public class MainLoop<T> : IMainLoop<T>
 
         var swCallbacks = Stopwatch.StartNew ();
 
-        TimedEvents.LockAndRunTimers ();
-
-        TimedEvents.LockAndRunIdles ();
+        TimedEvents.RunTimers ();
 
         Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds);
     }

+ 159 - 174
Terminal.Gui/Drivers/V2/V2.cd

@@ -6,7 +6,7 @@
   <Comment CommentText="Thread 2 - Main Loop which does everything else including output.  Deals with input exclusively through the input buffer. Is accessible externally e.g. to Application">
     <Position X="11.083" Y="3.813" Height="0.479" Width="5.325" />
   </Comment>
-  <Comment CommentText="Orchestrates the 2 main threads in Terminal.Gui">
+  <Comment CommentText="Orchestrates the 2 main threads in Terminal.Gui.Drivers">
     <Position X="6.5" Y="1.25" Height="0.291" Width="2.929" />
   </Comment>
   <Comment CommentText="Allows Views to work with new architecture without having to be rewritten.">
@@ -18,7 +18,7 @@
   <Comment CommentText="Mouse interpretation subsystem">
     <Position X="13.271" Y="9.896" Height="0.396" Width="2.075" />
   </Comment>
-  <Comment CommentText="In Terminal.Gui views get things done almost exclusively by calling static methods on Application e.g. RequestStop, Run, Refresh etc">
+  <Comment CommentText="In Terminal.Gui.Drivers views get things done almost exclusively by calling static methods on Application e.g. RequestStop, Run, Refresh etc">
     <Position X="0.5" Y="3.75" Height="1.146" Width="1.7" />
   </Comment>
   <Comment CommentText="Static record of system state and static gateway API for everything you ever need.">
@@ -27,44 +27,33 @@
   <Comment CommentText="Forwarded subset of gateway functionality. These exist to allow ''subclassing' Application.  Note that most methods 'ping pong' a lot back to main gateway submethods e.g. to manipulate TopLevel etc">
     <Position X="2.895" Y="5.417" Height="1.063" Width="2.992" />
   </Comment>
-  <Class Name="Terminal.Gui.WindowsInput" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.WindowsInput" Collapsed="true">
     <Position X="11.5" Y="3" Width="1.75" />
     <TypeIdentifier>
       <HashCode>QIAACAAAACAEAAAAAAAAAAAkAAAAAAAAAwAAAAAAABA=</HashCode>
-      <FileName>ConsoleDrivers\V2\WindowsInput.cs</FileName>
+      <FileName>Drivers\V2\WindowsInput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.NetInput" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.NetInput" Collapsed="true">
     <Position X="13.25" Y="3" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAACAEAAAAQAAAAAAgAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\NetInput.cs</FileName>
+      <FileName>Drivers\V2\NetInput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.ConsoleInput&lt;T&gt;" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.ConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="12.5" Y="2" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAACAEAQAAAAAAAAAgACAAAAAAAAAAAAAAAAo=</HashCode>
-      <FileName>ConsoleDrivers\V2\ConsoleInput.cs</FileName>
+      <FileName>Drivers\V2\ConsoleInput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
+  <Class Name="Terminal.Gui.Drivers.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="11" Y="4.75" Width="1.5" />
-    <AssociationLine Name="TimedEvents" Type="Terminal.Gui.ITimedEvents" ManuallyRouted="true">
-      <Path>
-        <Point X="11.312" Y="5.312" />
-        <Point X="11.312" Y="6.292" />
-        <Point X="10" Y="6.292" />
-        <Point X="10" Y="7.25" />
-      </Path>
-      <MemberNameLabel ManuallyPlaced="true">
-        <Position X="-1.015" Y="1.019" />
-      </MemberNameLabel>
-    </AssociationLine>
-    <AssociationLine Name="OutputBuffer" Type="Terminal.Gui.IOutputBuffer" ManuallyRouted="true">
+    <AssociationLine Name="OutputBuffer" Type="Terminal.Gui.Drivers.IOutputBuffer" ManuallyRouted="true">
       <Path>
         <Point X="11.718" Y="5.312" />
         <Point X="11.718" Y="7.25" />
@@ -73,7 +62,7 @@
         <Position X="0.027" Y="0.102" />
       </MemberNameLabel>
     </AssociationLine>
-    <AssociationLine Name="Out" Type="Terminal.Gui.IConsoleOutput" ManuallyRouted="true">
+    <AssociationLine Name="Out" Type="Terminal.Gui.Drivers.IConsoleOutput" ManuallyRouted="true">
       <Path>
         <Point X="12.5" Y="5.125" />
         <Point X="12.5" Y="5.792" />
@@ -82,7 +71,7 @@
         <Point X="14" Y="7.846" />
       </Path>
     </AssociationLine>
-    <AssociationLine Name="AnsiRequestScheduler" Type="Terminal.Gui.AnsiRequestScheduler" ManuallyRouted="true">
+    <AssociationLine Name="AnsiRequestScheduler" Type="Terminal.Gui.Drivers.AnsiRequestScheduler" ManuallyRouted="true">
       <Path>
         <Point X="11.75" Y="4.75" />
         <Point X="11.75" Y="4.39" />
@@ -93,7 +82,7 @@
         <Position X="0.11" Y="0.143" />
       </MemberNameLabel>
     </AssociationLine>
-    <AssociationLine Name="WindowSizeMonitor" Type="Terminal.Gui.IWindowSizeMonitor" ManuallyRouted="true">
+    <AssociationLine Name="WindowSizeMonitor" Type="Terminal.Gui.Drivers.IWindowSizeMonitor" ManuallyRouted="true">
       <Path>
         <Point X="12.125" Y="5.312" />
         <Point X="12.125" Y="7" />
@@ -105,7 +94,7 @@
         <Position X="0.047" Y="-0.336" />
       </MemberNameLabel>
     </AssociationLine>
-    <AssociationLine Name="ToplevelTransitionManager" Type="Terminal.Gui.IToplevelTransitionManager" ManuallyRouted="true">
+    <AssociationLine Name="ToplevelTransitionManager" Type="Terminal.Gui.Drivers.IToplevelTransitionManager" ManuallyRouted="true">
       <Path>
         <Point X="11" Y="5.031" />
         <Point X="11" Y="5.406" />
@@ -119,12 +108,11 @@
       </MemberNameLabel>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>QQQAAAAQACABJQQAABAAAQAAACAAAAACAAEAAACAEgg=</HashCode>
-      <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
+      <HashCode>QQQAAAAQACABJQQAABAAAQAAACAAAAACAIEAAAAAEgg=</HashCode>
+      <FileName>Drivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Field Name="ToplevelTransitionManager" />
-      <Property Name="TimedEvents" />
       <Property Name="InputProcessor" />
       <Property Name="OutputBuffer" />
       <Property Name="Out" />
@@ -133,25 +121,25 @@
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.MainLoopCoordinator&lt;T&gt;">
+  <Class Name="Terminal.Gui.Drivers.MainLoopCoordinator&lt;T&gt;">
     <Position X="6.5" Y="2" Width="2" />
     <TypeIdentifier>
       <HashCode>IAAAIAEiCAIABAAAABQAAAAAABAAAQQAIQIABAAACgg=</HashCode>
-      <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
+      <FileName>Drivers\V2\MainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Field Name="_loop" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.AnsiResponseParser&lt;T&gt;" Collapsed="true">
     <Position X="19.5" Y="10" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAACBAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.OutputBuffer">
+  <Class Name="Terminal.Gui.Drivers.OutputBuffer">
     <Position X="11" Y="8.25" Width="1.5" />
     <Compartments>
       <Compartment Name="Fields" Collapsed="true" />
@@ -159,29 +147,29 @@
     </Compartments>
     <TypeIdentifier>
       <HashCode>AwAAAAAAAIAAAECIBgAEQIAAAAEMRgAACAAAKABAgAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\OutputBuffer.cs</FileName>
+      <FileName>Drivers\V2\OutputBuffer.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.NetOutput" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.NetOutput" Collapsed="true">
     <Position X="14.75" Y="8.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AEAAAAAAACAAAAAAAAAAAAAAAAAAQAAAMACAAAEAgAk=</HashCode>
-      <FileName>ConsoleDrivers\V2\NetOutput.cs</FileName>
+      <HashCode>AEAAAAAAACAAAAAAAAAQAAAAAAAAQAAAMACAAAEAgAk=</HashCode>
+      <FileName>Drivers\V2\NetOutput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.WindowsOutput" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.WindowsOutput" Collapsed="true">
     <Position X="13.25" Y="8.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AEAAABACACAAhAAAAAAAACCAAAgAQAAIMAAAAAEAgAQ=</HashCode>
-      <FileName>ConsoleDrivers\V2\WindowsOutput.cs</FileName>
+      <HashCode>AEAAABACACAAhAAAAAAQACCAAAgAYAAIMAAAAAEAgAQ=</HashCode>
+      <FileName>Drivers\V2\WindowsOutput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.InputProcessor&lt;T&gt;" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.InputProcessor&lt;T&gt;" Collapsed="true">
     <Position X="16.5" Y="4.75" Width="2" />
-    <AssociationLine Name="_mouseInterpreter" Type="Terminal.Gui.MouseInterpreter" ManuallyRouted="true">
+    <AssociationLine Name="_mouseInterpreter" Type="Terminal.Gui.Drivers.MouseInterpreter" ManuallyRouted="true">
       <Path>
         <Point X="17.75" Y="5.312" />
         <Point X="17.75" Y="10.031" />
@@ -192,7 +180,7 @@
     </AssociationLine>
     <TypeIdentifier>
       <HashCode>AQAkEAAAAASAiAAEAgwgAAAABAIAAAAAAAAAAAAEAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
+      <FileName>Drivers\V2\InputProcessor.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Field Name="_mouseInterpreter" />
@@ -201,28 +189,28 @@
     </ShowAsAssociation>
     <Lollipop Position="0.1" />
   </Class>
-  <Class Name="Terminal.Gui.NetInputProcessor" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.NetInputProcessor" Collapsed="true">
     <Position X="17.75" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAACBAAAgAAAEAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\NetInputProcessor.cs</FileName>
+      <FileName>Drivers\V2\NetInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.WindowsInputProcessor" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.WindowsInputProcessor" Collapsed="true">
     <Position X="15.75" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AQAAAAAAAAAACAAAAgAAAAAAAgAEAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\WindowsInputProcessor.cs</FileName>
+      <FileName>Drivers\V2\WindowsInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.AnsiMouseParser">
+  <Class Name="Terminal.Gui.Drivers.AnsiMouseParser">
     <Position X="23.5" Y="9.75" Width="1.75" />
     <TypeIdentifier>
       <HashCode>BAAAAAAAAAgAAAAAAAAAAAAAIAAAAAAAQAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiMouseParser.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\AnsiMouseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.ConsoleDriverFacade&lt;T&gt;">
+  <Class Name="Terminal.Gui.Drivers.ConsoleDriverFacade&lt;T&gt;">
     <Position X="6.5" Y="7.75" Width="2" />
     <Compartments>
       <Compartment Name="Methods" Collapsed="true" />
@@ -230,41 +218,41 @@
     </Compartments>
     <TypeIdentifier>
       <HashCode>AQcgAAAAAKBAgFEIBBgAQJEAAjkaQiIAGQADKABDgAQ=</HashCode>
-      <FileName>ConsoleDrivers\V2\ConsoleDriverFacade.cs</FileName>
+      <FileName>Drivers\V2\ConsoleDriverFacade.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Property Name="InputProcessor" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.AnsiRequestScheduler" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.AnsiRequestScheduler" Collapsed="true">
     <Position X="19.5" Y="4.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAACAAIAAAIAACAESQAAQAACGAAAAAAAAAAAAAQQA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiRequestScheduler.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\AnsiRequestScheduler.cs</FileName>
     </TypeIdentifier>
     <ShowAsCollectionAssociation>
       <Property Name="QueuedRequests" />
     </ShowAsCollectionAssociation>
   </Class>
-  <Class Name="Terminal.Gui.AnsiResponseParserBase" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.AnsiResponseParserBase" Collapsed="true">
     <Position X="20.25" Y="9" Width="2" />
-    <AssociationLine Name="_mouseParser" Type="Terminal.Gui.AnsiMouseParser" FixedFromPoint="true" FixedToPoint="true">
+    <AssociationLine Name="_mouseParser" Type="Terminal.Gui.Drivers.AnsiMouseParser" FixedFromPoint="true" FixedToPoint="true">
       <Path>
         <Point X="22.25" Y="9.438" />
         <Point X="24.375" Y="9.438" />
         <Point X="24.375" Y="9.75" />
       </Path>
     </AssociationLine>
-    <AssociationLine Name="_keyboardParser" Type="Terminal.Gui.AnsiKeyboardParser" FixedFromPoint="true">
+    <AssociationLine Name="_keyboardParser" Type="Terminal.Gui.Drivers.AnsiKeyboardParser" FixedFromPoint="true">
       <Path>
         <Point X="22.25" Y="9.375" />
         <Point X="25.5" Y="9.375" />
       </Path>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>UAiASAAAEICQALAAQAAAKAAAoAIAAABAAQIAJiAQASQ=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
+      <HashCode>UAiASAAAEICQALCAQAAAKAAAoAIAAABAAQIAJiAQASQ=</HashCode>
+      <FileName>Drivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Field Name="_mouseParser" />
@@ -273,296 +261,293 @@
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.MouseInterpreter">
+  <Class Name="Terminal.Gui.Drivers.MouseInterpreter">
     <Position X="13.25" Y="10.5" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAABAAAAAAAAAAAAgAAAAAAACAAAAAAAAUAAAAIAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\MouseInterpreter.cs</FileName>
+      <FileName>Drivers\V2\MouseInterpreter.cs</FileName>
     </TypeIdentifier>
     <ShowAsCollectionAssociation>
       <Field Name="_buttonStates" />
     </ShowAsCollectionAssociation>
   </Class>
-  <Class Name="Terminal.Gui.MouseButtonStateEx">
+  <Class Name="Terminal.Gui.Drivers.MouseButtonStateEx">
     <Position X="16.5" Y="10.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAMwAIAAAAAAAAAAAABCAAAAAAAAABAAEAAg=</HashCode>
-      <FileName>ConsoleDrivers\V2\MouseButtonStateEx.cs</FileName>
+      <FileName>Drivers\V2\MouseButtonStateEx.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.StringHeld" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.StringHeld" Collapsed="true">
     <Position X="21.5" Y="11" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAIAACAAAAAAAIBAAAAAAACAAAAAAAgAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\StringHeld.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\StringHeld.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.GenericHeld&lt;T&gt;" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.GenericHeld&lt;T&gt;" Collapsed="true">
     <Position X="19.75" Y="11" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAgAIAACAAAAAAAIBAAAAAAACAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\GenericHeld.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\GenericHeld.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.AnsiEscapeSequenceRequest">
+  <Class Name="Terminal.Gui.Drivers.AnsiEscapeSequenceRequest">
     <Position X="23" Y="4.5" Width="2.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAEAAAAAAAEAAAAACAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiEscapeSequenceRequest.cs</FileName>
+      <FileName>Drivers\AnsiEscapeSequenceRequest.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.AnsiEscapeSequence" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.AnsiEscapeSequence" Collapsed="true">
     <Position X="23" Y="3.75" Width="2.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAgAAEAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiEscapeSequence.cs</FileName>
+      <FileName>Drivers\AnsiEscapeSequence.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.AnsiResponseParser" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.AnsiResponseParser" Collapsed="true">
     <Position X="21.5" Y="10" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAgACBAAAAACBAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
-    </TypeIdentifier>
-  </Class>
-  <Class Name="Terminal.Gui.Application" Collapsed="true">
-    <Position X="0.5" Y="0.5" Width="1.5" />
-    <TypeIdentifier>
-      <HashCode>hEK4FAgAqARIspQeBwoUgTGgACNL0AIAESLKoggBSw8=</HashCode>
-      <FileName>Application\Application.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.ApplicationImpl" Collapsed="true">
-    <Position X="2.75" Y="4.5" Width="1.5" />
-    <TypeIdentifier>
-      <HashCode>AABAAAAAIAAIAgQQAAAAAQAAAAAAAAAAQAAKgAAAAAI=</HashCode>
-      <FileName>Application\ApplicationImpl.cs</FileName>
-    </TypeIdentifier>
-    <ShowAsAssociation>
-      <Property Name="Instance" />
-    </ShowAsAssociation>
-    <Lollipop Position="0.2" />
-  </Class>
-  <Class Name="Terminal.Gui.ApplicationV2" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.ApplicationV2" Collapsed="true">
     <Position X="4.75" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>QAAAAAgABAEIBgAQAAAAAQBAAAAAgAEAAAAKgIAAAgI=</HashCode>
-      <FileName>ConsoleDrivers\V2\ApplicationV2.cs</FileName>
+      <HashCode>QAAgAAgABAEIBgAQAAAAAQAAAAAAgAEAAAAKAIAAEgI=</HashCode>
+      <FileName>Drivers\V2\ApplicationV2.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Field Name="_coordinator" />
     </ShowAsAssociation>
   </Class>
-  <Class Name="Terminal.Gui.View" Collapsed="true">
-    <Position X="0.5" Y="3" Width="1.5" />
-    <TypeIdentifier>
-      <HashCode>3/v2dzPLvbb/5+LOHuv1x0dem3Y57v/8c6afz2/e/Y8=</HashCode>
-      <FileName>View\View.Adornments.cs</FileName>
-    </TypeIdentifier>
-    <Lollipop Position="0.2" />
-  </Class>
-  <Class Name="Terminal.Gui.WindowsKeyConverter" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.WindowsKeyConverter" Collapsed="true">
     <Position X="16" Y="7.5" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\WindowsKeyConverter.cs</FileName>
+      <FileName>Drivers\V2\WindowsKeyConverter.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.NetKeyConverter" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.NetKeyConverter" Collapsed="true">
     <Position X="17.75" Y="7.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\NetKeyConverter.cs</FileName>
+      <FileName>Drivers\V2\NetKeyConverter.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.AnsiKeyboardParser">
+  <Class Name="Terminal.Gui.Drivers.AnsiKeyboardParser">
     <Position X="25.5" Y="9.25" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAE=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\Keyboard\AnsiKeyboardParser.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\Keyboard\AnsiKeyboardParser.cs</FileName>
     </TypeIdentifier>
     <ShowAsCollectionAssociation>
       <Field Name="_patterns" />
     </ShowAsCollectionAssociation>
   </Class>
-  <Class Name="Terminal.Gui.ToplevelTransitionManager" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.ToplevelTransitionManager" Collapsed="true">
     <Position X="9.25" Y="13.75" Width="2.25" />
     <TypeIdentifier>
       <HashCode>AIAAAAAAAAAAAAEAAAAAAAAAAEIAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\ToplevelTransitionManager.cs</FileName>
+      <FileName>Drivers\V2\ToplevelTransitionManager.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.Logging" Collapsed="true">
-    <Position X="0.5" Y="5.25" Width="1.5" />
-    <TypeIdentifier>
-      <HashCode>AAAAAAAAAAIgAAAAAAEQAAAAAAAAABAAgAAAAAAAEAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\Logging.cs</FileName>
-    </TypeIdentifier>
-  </Class>
-  <Class Name="Terminal.Gui.WindowSizeMonitor" Collapsed="true" BaseTypeListCollapsed="true">
+  <Class Name="Terminal.Gui.Drivers.WindowSizeMonitor" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="13.25" Y="14" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAgAAAAAAAAAAEAAAAABAAAAAACAAAAAAAAAAAACA=</HashCode>
-      <FileName>ConsoleDrivers\V2\WindowSizeMonitor.cs</FileName>
+      <FileName>Drivers\V2\WindowSizeMonitor.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.AnsiKeyboardParserPattern" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.AnsiKeyboardParserPattern" Collapsed="true">
     <Position X="28.5" Y="9.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAACIAAAAAAAAAAAAAAAAAQQAAAAAAAAAAAAAAAACAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\Keyboard\AnsiKeyboardParserPattern.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\Keyboard\AnsiKeyboardParserPattern.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.CsiKeyPattern" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.CsiKeyPattern" Collapsed="true">
     <Position X="25.5" Y="10.75" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAACAAAAAAAAABAAAAAAAAAQAACAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\Keyboard\CsiKeyPattern.cs</FileName>
+      <HashCode>AAACQAAAAAAAAAAAAAAAAAAQAACAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>Drivers\AnsiResponseParser\Keyboard\CsiKeyPattern.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.EscAsAltPattern" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.EscAsAltPattern" Collapsed="true">
     <Position X="27.75" Y="10.75" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAACAAAAAAAAAAAAAAAAAAAQAACAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\Keyboard\EscAsAltPattern.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\Keyboard\EscAsAltPattern.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.Ss3Pattern" Collapsed="true">
+  <Class Name="Terminal.Gui.Drivers.Ss3Pattern" Collapsed="true">
     <Position X="29.5" Y="10.75" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAACAAAAAAAAAAAAAAAAAAAQAACAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\Keyboard\Ss3Pattern.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\Keyboard\Ss3Pattern.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;" Collapsed="true">
+  <Class Name="Terminal.Gui.App.ApplicationImpl" Collapsed="true">
+    <Position X="2.75" Y="4.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AABgAAAAIAAIAgQUAAAAAQAAAAAAAAAAQAAKAAAAEAI=</HashCode>
+      <FileName>App\ApplicationImpl.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.App.Application" Collapsed="true">
+    <Position X="0.5" Y="0.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>gEK4FIgYOAQIuhQeBwoUgSCgAAJL0AACESIKoAiBWw8=</HashCode>
+      <FileName>App\Application.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.ViewBase.View" Collapsed="true">
+    <Position X="0.5" Y="3" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>27u2V3Pfvf7/x/LOXur1x0de3zZt7v/8c+bfzX/e/c8=</HashCode>
+      <FileName>ViewBase\View.Adornments.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.App.Logging" Collapsed="true">
+    <Position X="0.5" Y="5.25" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAIEAAAAAIgAYAAAAEQABAAAAAAABAAgAAAAAAAEAA=</HashCode>
+      <FileName>App\Logging.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Interface Name="Terminal.Gui.Drivers.IConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="12.5" Y="1" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
-      <FileName>ConsoleDrivers\V2\IConsoleInput.cs</FileName>
+      <FileName>Drivers\V2\IConsoleInput.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;" Collapsed="true">
+  <Interface Name="Terminal.Gui.Drivers.IMainLoop&lt;T&gt;" Collapsed="true">
     <Position X="9.25" Y="4.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>QAQAAAAAAAABIQQAAAAAAAAAAAAAAAACAAAAAAAAEAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
+      <FileName>Drivers\V2\IMainLoop.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IConsoleOutput" Collapsed="true">
+  <Interface Name="Terminal.Gui.Drivers.IConsoleOutput" Collapsed="true">
     <Position X="14" Y="7.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAMAAAAAEAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IConsoleOutput.cs</FileName>
+      <FileName>Drivers\V2\IConsoleOutput.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IOutputBuffer" Collapsed="true">
+  <Interface Name="Terminal.Gui.Drivers.IOutputBuffer" Collapsed="true">
     <Position X="11" Y="7.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AQAAAAAAAIAAAEAIAAAAQIAAAAEMRgAACAAAKABAgAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IOutputBuffer.cs</FileName>
+      <FileName>Drivers\V2\IOutputBuffer.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IInputProcessor">
+  <Interface Name="Terminal.Gui.Drivers.IInputProcessor">
     <Position X="14" Y="4.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAkAAAAAACAgAAAAAggAAAABAIAAAAAAAAAAAAEAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IInputProcessor.cs</FileName>
+      <FileName>Drivers\V2\IInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IHeld">
+  <Interface Name="Terminal.Gui.Drivers.IHeld">
     <Position X="23.75" Y="6.5" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAIAACAAAAAAAIBAAAAAAACAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\IHeld.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\IHeld.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IAnsiResponseParser">
+  <Interface Name="Terminal.Gui.Drivers.IAnsiResponseParser">
     <Position X="20.25" Y="5.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAQAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAJAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\IAnsiResponseParser.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\IAnsiResponseParser.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Property Name="State" />
     </ShowAsAssociation>
   </Interface>
-  <Interface Name="Terminal.Gui.IApplication">
-    <Position X="3" Y="1" Width="1.5" />
-    <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAIAgQQAAAAAQAAAAAAAAAAAAAKgAAAAAI=</HashCode>
-      <FileName>Application\IApplication.cs</FileName>
-    </TypeIdentifier>
-  </Interface>
-  <Interface Name="Terminal.Gui.IMainLoopCoordinator" Collapsed="true">
+  <Interface Name="Terminal.Gui.Drivers.IMainLoopCoordinator" Collapsed="true">
     <Position X="6.5" Y="0.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQIAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IMainLoopCoordinator.cs</FileName>
+      <FileName>Drivers\V2\IMainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IWindowSizeMonitor" Collapsed="true">
+  <Interface Name="Terminal.Gui.Drivers.IWindowSizeMonitor" Collapsed="true">
     <Position X="13.25" Y="13" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAEAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IWindowSizeMonitor.cs</FileName>
-    </TypeIdentifier>
-  </Interface>
-  <Interface Name="Terminal.Gui.ITimedEvents">
-    <Position X="9.25" Y="7.25" Width="1.5" />
-    <Compartments>
-      <Compartment Name="Methods" Collapsed="true" />
-    </Compartments>
-    <TypeIdentifier>
-      <HashCode>BAAAIAAAAQAAAAAQACAAAIBAAQAAAAAAAAAIgAAAAAA=</HashCode>
-      <FileName>Application\ITimedEvents.cs</FileName>
+      <FileName>Drivers\V2\IWindowSizeMonitor.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IKeyConverter&lt;T&gt;" Collapsed="true">
+  <Interface Name="Terminal.Gui.Drivers.IKeyConverter&lt;T&gt;" Collapsed="true">
     <Position X="17" Y="6.5" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IKeyConverter.cs</FileName>
+      <FileName>Drivers\V2\IKeyConverter.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IToplevelTransitionManager">
+  <Interface Name="Terminal.Gui.Drivers.IToplevelTransitionManager">
     <Position X="9.25" Y="12" Width="2.25" />
     <TypeIdentifier>
       <HashCode>AIAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IToplevelTransitionManager.cs</FileName>
+      <FileName>Drivers\V2\IToplevelTransitionManager.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IConsoleDriverFacade">
+  <Interface Name="Terminal.Gui.Drivers.IConsoleDriverFacade">
     <Position X="4.5" Y="8.75" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IConsoleDriverFacade.cs</FileName>
+      <FileName>Drivers\V2\IConsoleDriverFacade.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.INetInput" Collapsed="true">
+  <Interface Name="Terminal.Gui.Drivers.INetInput" Collapsed="true">
     <Position X="14.25" Y="2" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\INetInput.cs</FileName>
+      <FileName>Drivers\V2\INetInput.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IWindowsInput" Collapsed="true">
+  <Interface Name="Terminal.Gui.Drivers.IWindowsInput" Collapsed="true">
     <Position X="10.75" Y="2" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IWindowsInput.cs</FileName>
+      <FileName>Drivers\V2\IWindowsInput.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
+  <Interface Name="Terminal.Gui.App.ITimedEvents">
+    <Position X="9.25" Y="7.25" Width="1.5" />
+    <Compartments>
+      <Compartment Name="Methods" Collapsed="true" />
+    </Compartments>
+    <TypeIdentifier>
+      <HashCode>CAIAAAAAAQAAAAAAAAAABEAAAAAABAAAAAAAAAAAAAA=</HashCode>
+      <FileName>App\ITimedEvents.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
+  <Interface Name="Terminal.Gui.App.IApplication">
+    <Position X="3" Y="1" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAgAAAAAAAIAgQUAAAAAQAAAAAAAAAAAAAKAAAAEAI=</HashCode>
+      <FileName>App\IApplication.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Enum Name="Terminal.Gui.AnsiResponseParserState">
+  <Enum Name="Terminal.Gui.Drivers.AnsiResponseParserState">
     <Position X="20.25" Y="7.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAACAAAAAAIAAIAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParserState.cs</FileName>
+      <FileName>Drivers\AnsiResponseParser\AnsiResponseParserState.cs</FileName>
     </TypeIdentifier>
   </Enum>
   <Font Name="Segoe UI" Size="9" />

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

@@ -895,51 +895,6 @@ internal class WindowsDriver : ConsoleDriver
         //buttonPressedCount = 0;
     }
 
-    private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
-    {
-        // When a user presses-and-holds, start generating pressed events every `startDelay`
-        // After `iterationsUntilFast` iterations, speed them up to `fastDelay` ms
-        const int START_DELAY = 500;
-        const int ITERATIONS_UNTIL_FAST = 4;
-        const int FAST_DELAY = 50;
-
-        int iterations = 0;
-        int delay = START_DELAY;
-        while (_isButtonPressed)
-        {
-            // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
-            View? view = Application.WantContinuousButtonPressedView;
-
-            if (view is null)
-            {
-                break;
-            }
-
-            if (iterations++ >= ITERATIONS_UNTIL_FAST)
-            {
-                delay = FAST_DELAY;
-            }
-            await Task.Delay (delay);
-
-            //Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}");
-            if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0)
-            {
-                Point pointMove = _pointMove;
-                // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
-                Application.Invoke (() =>
-                                    {
-                                        var me = new MouseEventArgs
-                                        {
-                                            ScreenPosition = pointMove,
-                                            Position = pointMove,
-                                            Flags = mouseFlag
-                                        };
-                                        OnMouseEvent (me);
-                                    });
-            }
-        }
-    }
-
     private void ResizeScreen ()
     {
         _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols];
@@ -990,7 +945,7 @@ internal class WindowsDriver : ConsoleDriver
         if (_isButtonDoubleClicked || _isOneFingerDoubleClicked)
         {
             // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
-            Application.MainLoop!.AddIdle (
+            Application.MainLoop!.TimedEvents.Add (TimeSpan.Zero,
                                           () =>
                                           {
                                               Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
@@ -1058,18 +1013,6 @@ internal class WindowsDriver : ConsoleDriver
 
             _lastMouseButtonPressed = mouseEvent.ButtonState;
             _isButtonPressed = true;
-
-            if ((mouseFlag & MouseFlags.ReportMousePosition) == 0)
-            {
-                // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
-                Application.MainLoop!.AddIdle (
-                                              () =>
-                                              {
-                                                  Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag));
-
-                                                  return false;
-                                              });
-            }
         }
         else if (_lastMouseButtonPressed != null
                  && mouseEvent.EventFlags == 0

+ 3 - 3
Terminal.Gui/Drivers/WindowsDriver/WindowsMainLoop.cs

@@ -73,7 +73,7 @@ internal class WindowsMainLoop : IMainLoopDriver
 #if HACK_CHECK_WINCHANGED
         _winChange.Set ();
 #endif
-        if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout))
+        if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimers (out int waitTimeout))
         {
             return true;
         }
@@ -102,9 +102,9 @@ internal class WindowsMainLoop : IMainLoopDriver
         if (!_eventReadyTokenSource.IsCancellationRequested)
         {
 #if HACK_CHECK_WINCHANGED
-            return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out _) || _winChanged;
+            return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimers (out _) || _winChanged;
 #else
-            return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out _);
+            return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimers (out _);
 #endif
         }
 

+ 3 - 5
Terminal.Gui/Terminal.Gui.csproj

@@ -62,9 +62,7 @@
         <PackageReference Include="Wcwidth" />
     </ItemGroup>
 	  <ItemGroup>
-    <ProjectReference Include="..\Terminal.Gui.Analyzers\Terminal.Gui.Analyzers.csproj"
-                      ReferenceOutputAssembly="false"
-                      OutputItemType="Analyzer" />
+    <ProjectReference Include="..\Terminal.Gui.Analyzers\Terminal.Gui.Analyzers.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
 	  </ItemGroup>
     <ItemGroup>
       <!-- Enable Nuget Source Link for github -->
@@ -205,7 +203,7 @@
         <Message Text="Copy completed successfully." Importance="high" />
 
         <!-- Install NuGet Package to Global Cache -->
-        <Exec Command="dotnet nuget push &quot;$(MSBuildThisFileDirectory)bin\$(Configuration)\Terminal.Gui.$(Version).nupkg&quot; --source &quot;$(UserProfile)\.nuget\packages&quot; --skip-duplicate" Condition=" '$(OS)' == 'Windows_NT' "/>
-        <Exec Command="dotnet nuget push &quot;$(MSBuildThisFileDirectory)bin\$(Configuration)/Terminal.Gui.$(Version).nupkg&quot; --source ~/.nuget/packages" Condition=" '$(OS)' != 'Windows_NT' "/>
+        <Exec Command="dotnet nuget push &quot;$(MSBuildThisFileDirectory)bin\$(Configuration)\Terminal.Gui.$(Version).nupkg&quot; --source &quot;$(UserProfile)\.nuget\packages&quot; --skip-duplicate" Condition=" '$(OS)' == 'Windows_NT' " />
+        <Exec Command="dotnet nuget push &quot;$(MSBuildThisFileDirectory)bin\$(Configuration)/Terminal.Gui.$(Version).nupkg&quot; --source ~/.nuget/packages" Condition=" '$(OS)' != 'Windows_NT' " />
     </Target>
 </Project>

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

@@ -431,9 +431,9 @@ public partial class Border
 
         Application.MouseEvent -= ApplicationOnMouseEvent;
 
-        if (Application.MouseGrabView == this && _dragPosition.HasValue)
+        if (Application.MouseGrabHandler.MouseGrabView == this && _dragPosition.HasValue)
         {
-            Application.UngrabMouse ();
+            Application.MouseGrabHandler.UngrabMouse ();
         }
 
         // Clean up all arrangement buttons
@@ -498,7 +498,7 @@ public partial class Border
                 // Set the start grab point to the Frame coords
                 _startGrabPoint = new (mouseEvent.Position.X + Frame.X, mouseEvent.Position.Y + Frame.Y);
                 _dragPosition = mouseEvent.Position;
-                Application.GrabMouse (this);
+                Application.MouseGrabHandler.GrabMouse (this);
 
                 // Determine the mode based on where the click occurred
                 ViewArrangement arrangeMode = DetermineArrangeModeFromClick ();
@@ -511,7 +511,7 @@ public partial class Border
             return true;
         }
 
-        if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && Application.MouseGrabView == this)
+        if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && Application.MouseGrabHandler.MouseGrabView == this)
         {
             if (_dragPosition.HasValue)
             {
@@ -523,7 +523,7 @@ public partial class Border
         if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue)
         {
             _dragPosition = null;
-            Application.UngrabMouse ();
+            Application.MouseGrabHandler.UngrabMouse ();
 
             EndArrangeMode ();
 
@@ -763,7 +763,7 @@ public partial class Border
 
     private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e)
     {
-        if (Application.MouseGrabView == this && _dragPosition.HasValue)
+        if (Application.MouseGrabHandler.MouseGrabView == this && _dragPosition.HasValue)
         {
             e.Cancel = true;
         }
@@ -771,7 +771,7 @@ public partial class Border
 
     private void Application_UnGrabbingMouse (object? sender, GrabMouseEventArgs e)
     {
-        if (Application.MouseGrabView == this && _dragPosition.HasValue)
+        if (Application.MouseGrabHandler.MouseGrabView == this && _dragPosition.HasValue)
         {
             e.Cancel = true;
         }
@@ -784,8 +784,8 @@ public partial class Border
     /// <inheritdoc/>
     protected override void Dispose (bool disposing)
     {
-        Application.GrabbingMouse -= Application_GrabbingMouse;
-        Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
+        Application.MouseGrabHandler.GrabbingMouse -= Application_GrabbingMouse;
+        Application.MouseGrabHandler.UnGrabbingMouse -= Application_UnGrabbingMouse;
 
         _dragPosition = null;
         base.Dispose (disposing);

+ 2 - 2
Terminal.Gui/ViewBase/Adornment/Border.cs

@@ -50,8 +50,8 @@ public partial class Border : Adornment
         CanFocus = false;
         TabStop = TabBehavior.TabGroup;
 
-        Application.GrabbingMouse += Application_GrabbingMouse;
-        Application.UnGrabbingMouse += Application_UnGrabbingMouse;
+        Application.MouseGrabHandler.GrabbingMouse += Application_GrabbingMouse;
+        Application.MouseGrabHandler.UnGrabbingMouse += Application_UnGrabbingMouse;
 
         ThicknessChanged += OnThicknessChanged;
     }

+ 35 - 0
Terminal.Gui/ViewBase/IMouseHeldDown.cs

@@ -0,0 +1,35 @@
+#nullable enable
+using System.ComponentModel;
+
+namespace Terminal.Gui.ViewBase;
+
+/// <summary>
+///     <para>
+///         Handler for raising periodic events while the mouse is held down.
+///         Typically, mouse button only needs to be pressed down in a view
+///         to begin this event after which it can be moved elsewhere.
+///     </para>
+///     <para>
+///         Common use cases for this includes holding a button down to increase
+///         a counter (e.g. in <see cref="NumericUpDown"/>).
+///     </para>
+/// </summary>
+public interface IMouseHeldDown : IDisposable
+{
+    /// <summary>
+    ///     Periodically raised when the mouse is pressed down inside the view <see cref="View"/>.
+    /// </summary>
+    public event EventHandler<CancelEventArgs> MouseIsHeldDownTick;
+
+    /// <summary>
+    ///     Call to indicate that the mouse has been pressed down and any relevant actions should
+    ///     be undertaken (start timers, <see cref="IMouseGrabHandler.GrabMouse"/> etc).
+    /// </summary>
+    void Start ();
+
+    /// <summary>
+    ///     Call to indicate that the mouse has been released and any relevant actions should
+    ///     be undertaken (stop timers, <see cref="IMouseGrabHandler.UngrabMouse"/> etc).
+    /// </summary>
+    void Stop ();
+}

+ 111 - 0
Terminal.Gui/ViewBase/MouseHeldDown.cs

@@ -0,0 +1,111 @@
+#nullable enable
+using System.ComponentModel;
+
+namespace Terminal.Gui.ViewBase;
+
+/// <summary>
+///     INTERNAL: Manages the logic for handling a "mouse held down" state on a View. It is used to
+///     repeatedly trigger an action (via events) while the mouse button is held down, such as for auto-repeat in
+///     scrollbars or buttons.
+/// </summary>
+internal class MouseHeldDown : IMouseHeldDown
+{
+    public MouseHeldDown (View host, ITimedEvents? timedEvents, IMouseGrabHandler? mouseGrabber)
+    {
+        _mouseGrabView = host;
+        _timedEvents = timedEvents;
+        _mouseGrabber = mouseGrabber;
+        _smoothTimeout = new (TimeSpan.FromMilliseconds (500), TimeSpan.FromMilliseconds (50), 0.5, TickWhileMouseIsHeldDown);
+    }
+
+    private readonly View _mouseGrabView;
+    private readonly ITimedEvents? _timedEvents;
+    private readonly IMouseGrabHandler? _mouseGrabber;
+
+    private readonly SmoothAcceleratingTimeout _smoothTimeout;
+    private bool _isDown;
+    private object? _timeout;
+
+    public event EventHandler<CancelEventArgs>? MouseIsHeldDownTick;
+
+    public void Start ()
+    {
+        if (_isDown)
+        {
+            return;
+        }
+
+        _isDown = true;
+        _mouseGrabber?.GrabMouse (_mouseGrabView);
+
+        // Then periodic ticks
+        _timeout = _timedEvents?.Add (_smoothTimeout);
+    }
+
+    public void Stop ()
+    {
+        _smoothTimeout.Reset ();
+
+        if (_mouseGrabber?.MouseGrabView == _mouseGrabView)
+        {
+            _mouseGrabber?.UngrabMouse ();
+        }
+
+        if (_timeout != null)
+        {
+            _timedEvents?.Remove (_timeout);
+        }
+
+        _mouseGrabView.MouseState = MouseState.None;
+        _isDown = false;
+    }
+
+    public void Dispose ()
+    {
+        if (_mouseGrabber?.MouseGrabView == _mouseGrabView)
+        {
+            Stop ();
+        }
+    }
+
+    protected virtual bool OnMouseIsHeldDownTick (CancelEventArgs eventArgs) { return false; }
+
+    private bool RaiseMouseIsHeldDownTick ()
+    {
+        CancelEventArgs args = new ();
+
+        args.Cancel = OnMouseIsHeldDownTick (args) || args.Cancel;
+
+        if (!args.Cancel && MouseIsHeldDownTick is { })
+        {
+            MouseIsHeldDownTick?.Invoke (this, args);
+        }
+
+        // User event cancelled the mouse held down status so
+        // stop the currently running operation.
+        if (args.Cancel)
+        {
+            Stop ();
+        }
+
+        return args.Cancel;
+    }
+
+    private bool TickWhileMouseIsHeldDown ()
+    {
+        Logging.Debug ("Raising TickWhileMouseIsHeldDown...");
+
+        if (_isDown)
+        {
+            _smoothTimeout.AdvanceStage ();
+            RaiseMouseIsHeldDownTick ();
+        }
+        else
+        {
+            _smoothTimeout.Reset ();
+            Stop ();
+        }
+
+        return _isDown;
+    }
+}

+ 26 - 11
Terminal.Gui/ViewBase/View.Mouse.cs

@@ -5,11 +5,18 @@ namespace Terminal.Gui.ViewBase;
 
 public partial class View // Mouse APIs
 {
+    /// <summary>
+    /// Handles <see cref="WantContinuousButtonPressed"/>, we have detected a button
+    /// down in the view and have grabbed the mouse.
+    /// </summary>
+    public IMouseHeldDown? MouseHeldDown { get; set; }
+
     /// <summary>Gets the mouse bindings for this view.</summary>
     public MouseBindings MouseBindings { get; internal set; } = null!;
 
     private void SetupMouse ()
     {
+        MouseHeldDown = new MouseHeldDown (this, Application.TimedEvents,Application.MouseGrabHandler);
         MouseBindings = new ();
 
         // TODO: Should the default really work with any button or just button1?
@@ -307,6 +314,19 @@ public partial class View // Mouse APIs
     /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
     public bool RaiseMouseEvent (MouseEventArgs mouseEvent)
     {
+        // TODO: probably this should be moved elsewhere, please advise
+        if (WantContinuousButtonPressed && MouseHeldDown != null)
+        {
+            if (mouseEvent.IsPressed)
+            {
+                MouseHeldDown.Start ();
+            }
+            else
+            {
+                MouseHeldDown.Stop ();
+            }
+        }
+
         if (OnMouseEvent (mouseEvent) || mouseEvent.Handled)
         {
             return true;
@@ -355,7 +375,7 @@ public partial class View // Mouse APIs
 
         if (mouseEvent.IsReleased)
         {
-            if (Application.MouseGrabView == this)
+            if (Application.MouseGrabHandler.MouseGrabView == this)
             {
                 //Logging.Debug ($"{Id} - {MouseState}");
                 MouseState &= ~MouseState.Pressed;
@@ -387,9 +407,9 @@ public partial class View // Mouse APIs
         if (mouseEvent.IsPressed)
         {
             // The first time we get pressed event, grab the mouse and set focus
-            if (Application.MouseGrabView != this)
+            if (Application.MouseGrabHandler.MouseGrabView != this)
             {
-                Application.GrabMouse (this);
+                Application.MouseGrabHandler.GrabMouse (this);
 
                 if (!HasFocus && CanFocus)
                 {
@@ -425,11 +445,6 @@ public partial class View // Mouse APIs
                 }
             }
 
-            if (WantContinuousButtonPressed && Application.MouseGrabView == this)
-            {
-                return RaiseMouseClickEvent (mouseEvent);
-            }
-
             return mouseEvent.Handled = true;
         }
 
@@ -526,10 +541,10 @@ public partial class View // Mouse APIs
     {
         mouseEvent.Handled = false;
 
-        if (Application.MouseGrabView == this && mouseEvent.IsSingleClicked)
+        if (Application.MouseGrabHandler.MouseGrabView == this && mouseEvent.IsSingleClicked)
         {
             // We're grabbed. Clicked event comes after the last Release. This is our signal to ungrab
-            Application.UngrabMouse ();
+            Application.MouseGrabHandler.UngrabMouse ();
 
             // TODO: Prove we need to unset MouseState.Pressed and MouseState.PressedOutside here
             // TODO: There may be perf gains if we don't unset these flags here
@@ -680,4 +695,4 @@ public partial class View // Mouse APIs
     #endregion MouseState Handling
 
     private void DisposeMouse () { }
-}
+}

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

@@ -71,14 +71,9 @@ public partial class View : IDisposable, ISupportInitializeNotification
             DisposeAdornments ();
             DisposeScrollBars ();
 
-            if (Application.MouseGrabView == this)
+            if (Application.MouseGrabHandler.MouseGrabView == this)
             {
-                Application.UngrabMouse ();
-            }
-
-            if (Application.WantContinuousButtonPressedView == this)
-            {
-                Application.WantContinuousButtonPressedView = null;
+                Application.MouseGrabHandler.UngrabMouse ();
             }
 
             for (int i = InternalSubViews.Count - 1; i >= 0; i--)

+ 10 - 10
Terminal.Gui/Views/Autocomplete/Autocomplete.cd

@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <ClassDiagram MajorVersion="1" MinorVersion="1">
-  <Class Name="Terminal.Gui.AppendAutocomplete" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.AppendAutocomplete" Collapsed="true">
     <Position X="0.5" Y="6.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAgAABAAQIAAAAAAAAAAAAABAAAIAQAgAEIAggAIAA=</HashCode>
       <FileName>Core\Autocomplete\AppendAutocomplete.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.AutocompleteBase" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.AutocompleteBase" Collapsed="true">
     <Position X="1.75" Y="5.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAQgAAAAAUAAIAAAAAIAAAAAAAEAIAQIgQAIQAAAMBA=</HashCode>
@@ -15,10 +15,10 @@
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.PopupAutocomplete" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.PopupAutocomplete" Collapsed="true">
     <Position X="2.75" Y="6.5" Width="1.5" />
     <NestedTypes>
-      <Class Name="Terminal.Gui.PopupAutocomplete.Popup" Collapsed="true">
+      <Class Name="Terminal.Gui.Views.PopupAutocomplete.Popup" Collapsed="true">
         <TypeIdentifier>
           <NewMemberFileName>Core\Autocomplete\PopupAutocomplete.cs</NewMemberFileName>
         </TypeIdentifier>
@@ -29,7 +29,7 @@
       <FileName>Core\Autocomplete\PopupAutocomplete.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.SingleWordSuggestionGenerator" BaseTypeListCollapsed="true">
+  <Class Name="Terminal.Gui.Views.SingleWordSuggestionGenerator" BaseTypeListCollapsed="true">
     <Position X="6.25" Y="3.5" Width="3" />
     <TypeIdentifier>
       <HashCode>CEAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAIAA=</HashCode>
@@ -37,28 +37,28 @@
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.Suggestion">
+  <Class Name="Terminal.Gui.Views.Suggestion">
     <Position X="4.5" Y="2.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAEAAAAAABAAAAAAAAAAAAAAAAAAAAAE=</HashCode>
       <FileName>Core\Autocomplete\Suggestion.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.TextFieldAutocomplete" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.TextFieldAutocomplete" Collapsed="true">
     <Position X="1.5" Y="7.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAgAAAAAAAAAA=</HashCode>
       <FileName>Views\TextField.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.TextViewAutocomplete" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.TextViewAutocomplete" Collapsed="true">
     <Position X="3.75" Y="7.5" Width="2.25" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAgAAAAAAAAAA=</HashCode>
       <FileName>Views\TextView.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Interface Name="Terminal.Gui.IAutocomplete">
+  <Interface Name="Terminal.Gui.Views.IAutocomplete">
     <Position X="1.75" Y="0.5" Width="2.5" />
     <TypeIdentifier>
       <HashCode>AAQgAAAAAUAAIAAAAAAAAAAAAAEAIAQIgQAIQAAAMBA=</HashCode>
@@ -68,7 +68,7 @@
       <Property Name="SuggestionGenerator" />
     </ShowAsAssociation>
   </Interface>
-  <Interface Name="Terminal.Gui.ISuggestionGenerator">
+  <Interface Name="Terminal.Gui.Views.ISuggestionGenerator">
     <Position X="6.25" Y="1.75" Width="2.25" />
     <TypeIdentifier>
       <HashCode>AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAA=</HashCode>

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

@@ -125,7 +125,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
             {
                 Visible = true;
                 HostControl?.SetNeedsDraw ();
-                Application.UngrabMouse ();
+                Application.MouseGrabHandler.UngrabMouse ();
 
                 return false;
             }

+ 5 - 0
Terminal.Gui/Views/Button.cs

@@ -69,6 +69,11 @@ public class Button : View, IDesignable
 
         base.ShadowStyle = DefaultShadow;
         HighlightStates = DefaultHighlightStates;
+
+        if (MouseHeldDown != null)
+        {
+            MouseHeldDown.MouseIsHeldDownTick += (_,_) => RaiseAccepting (null);
+        }
     }
 
     private bool? HandleHotKeyCommand (ICommandContext commandContext)

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

@@ -767,7 +767,7 @@ public class CharMap : View, IDesignable
         }
 
         // BUGBUG: This is a workaround for some weird ScrollView related mouse grab bug
-        Application.GrabMouse (this);
+        Application.MouseGrabHandler.GrabMouse (this);
     }
 
     #endregion Details Dialog

+ 29 - 29
Terminal.Gui/Views/CollectionNavigation/CollectionNavigation.cd

@@ -9,7 +9,7 @@
   <Comment CommentText="Shared matching component (users should provide alternative implementations of this class if they want to modify collection navigation behaviour)">
     <Position X="9.448" Y="0.5" Height="0.708" Width="3.169" />
   </Comment>
-  <Class Name="Terminal.Gui.CollectionNavigatorBase" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.CollectionNavigatorBase" Collapsed="true">
     <Position X="6.25" Y="1.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAgEAAAAAAAQAAAIAAEAAgAAAAAABAAEAAAAACwAAAA=</HashCode>
@@ -20,7 +20,7 @@
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.CollectionNavigator" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.CollectionNavigator" Collapsed="true">
     <Position X="4.5" Y="3.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAQAAAAAAAAgAAAAAAAAAEAAAAAAAAAAA=</HashCode>
@@ -28,7 +28,7 @@
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.DefaultCollectionNavigatorMatcher">
+  <Class Name="Terminal.Gui.Views.DefaultCollectionNavigatorMatcher">
     <Position X="9.5" Y="2.5" Width="2.75" />
     <TypeIdentifier>
       <HashCode>AAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAQA=</HashCode>
@@ -36,14 +36,14 @@
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.TableCollectionNavigator" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.TableCollectionNavigator" Collapsed="true">
     <Position X="4.75" Y="6.5" Width="2.25" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAEAAAAIAAAAAA=</HashCode>
       <FileName>Views\CollectionNavigation\TableCollectionNavigator.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.ListView" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.ListView" Collapsed="true">
     <Position X="0.5" Y="4.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAE+ASAkEnAAABAAKGAggYAZJAIAABEAcBAaAwAQIAA=</HashCode>
@@ -54,25 +54,7 @@
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.FileDialog" Collapsed="true">
-    <Position X="0.5" Y="5.5" Width="1.75" />
-    <Compartments>
-      <Compartment Name="Nested Types" Collapsed="false" />
-    </Compartments>
-    <TypeIdentifier>
-      <HashCode>iIY4LQFUHDKVIHIESBgigQcFT6GxhBDABGJItBQAwAQ=</HashCode>
-      <FileName>Views\FileDialog.cs</FileName>
-    </TypeIdentifier>
-    <Lollipop Position="0.2" />
-  </Class>
-  <Class Name="Terminal.Gui.FileDialogCollectionNavigator" Collapsed="true">
-    <Position X="4.75" Y="5.5" Width="2.25" />
-    <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAA=</HashCode>
-      <FileName>Views\FileDialogCollectionNavigator.cs</FileName>
-    </TypeIdentifier>
-  </Class>
-  <Class Name="Terminal.Gui.TableView" Collapsed="true" BaseTypeListCollapsed="true">
+  <Class Name="Terminal.Gui.Views.TableView" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="0.5" Y="6.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>QwUeAxwgICIAcABIABeR0oBAkhoFGGOBDABgAN3oPEI=</HashCode>
@@ -80,7 +62,7 @@
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.TreeView" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.TreeView" Collapsed="true">
     <Position X="0.5" Y="3" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAA=</HashCode>
@@ -88,7 +70,7 @@
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.TreeView&lt;T&gt;" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.TreeView&lt;T&gt;" Collapsed="true">
     <Position X="0.5" Y="2" Width="1.5" />
     <TypeIdentifier>
       <HashCode>UwAGySBgBSBGMAQgIiCaBDUItJIBSAWwRMQOSgQCwJI=</HashCode>
@@ -99,21 +81,39 @@
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
-  <Interface Name="Terminal.Gui.ICollectionNavigatorMatcher" Collapsed="true">
+  <Class Name="Terminal.Gui.Views.FileDialog" Collapsed="true">
+    <Position X="0.5" Y="5.5" Width="1.75" />
+    <Compartments>
+      <Compartment Name="Nested Types" Collapsed="false" />
+    </Compartments>
+    <TypeIdentifier>
+      <HashCode>iIY4LQFUHDKVIHIESBoigQcFT6GxhBDABGJItBQAwAQ=</HashCode>
+      <FileName>Views\FileDialogs\FileDialog.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.Views.FileDialogCollectionNavigator" Collapsed="true">
+    <Position X="4.75" Y="5.5" Width="2.25" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAA=</HashCode>
+      <FileName>Views\FileDialogs\FileDialogCollectionNavigator.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Interface Name="Terminal.Gui.Views.ICollectionNavigatorMatcher" Collapsed="true">
     <Position X="9.5" Y="1.5" Width="2.75" />
     <TypeIdentifier>
       <HashCode>AAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAA=</HashCode>
       <FileName>Views\CollectionNavigation\ICollectionNavigatorMatcher.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IListCollectionNavigator" Collapsed="true">
+  <Interface Name="Terminal.Gui.Views.IListCollectionNavigator" Collapsed="true">
     <Position X="3.75" Y="2.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>Views\CollectionNavigation\IListCollectionNavigator.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.ICollectionNavigator" Collapsed="true">
+  <Interface Name="Terminal.Gui.Views.ICollectionNavigator" Collapsed="true">
     <Position X="3.75" Y="1.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAgAAAAAAAAAAAAIAAAAAAAAAAAABAAAAAAAACgAAAA=</HashCode>

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

@@ -958,7 +958,7 @@ public class ComboBox : View, IDesignable
                 {
                     _isFocusing = true;
                     _highlighted = _container.SelectedItem;
-                    Application.GrabMouse (this);
+                    Application.MouseGrabHandler.GrabMouse (this);
                 }
             }
             else
@@ -967,7 +967,7 @@ public class ComboBox : View, IDesignable
                 {
                     _isFocusing = false;
                     _highlighted = _container.SelectedItem;
-                    Application.UngrabMouse ();
+                    Application.MouseGrabHandler.UngrabMouse ();
                 }
             }
         }

+ 9 - 13
Terminal.Gui/Views/DatePicker.cs

@@ -227,13 +227,7 @@ public class DatePicker : View
             NoDecorations = true,
             ShadowStyle = ShadowStyle.None
         };
-
-        _previousMonthButton.Accepting += (sender, e) =>
-                                        {
-                                            Date = _date.AddMonths (-1);
-                                            CreateCalendar ();
-                                            _dateField.Date = Date;
-                                        };
+        _previousMonthButton.Accepting += (_, _) => AdjustMonth (-1);
 
         _nextMonthButton = new Button
         {
@@ -248,12 +242,7 @@ public class DatePicker : View
             ShadowStyle = ShadowStyle.None
         };
 
-        _nextMonthButton.Accepting += (sender, e) =>
-                                    {
-                                        Date = _date.AddMonths (1);
-                                        CreateCalendar ();
-                                        _dateField.Date = Date;
-                                    };
+        _nextMonthButton.Accepting += (_, _) => AdjustMonth (1);
 
         CreateCalendar ();
         SelectDayOnCalendar (_date.Day);
@@ -287,6 +276,13 @@ public class DatePicker : View
         Add (_dateLabel, _dateField, _calendar, _previousMonthButton, _nextMonthButton);
     }
 
+    private void AdjustMonth (int offset)
+    {
+        Date = _date.AddMonths (offset);
+        CreateCalendar ();
+        _dateField.Date = Date;
+    }
+
     /// <inheritdoc />
     protected override bool OnDrawingText () { return true; }
 

+ 84 - 113
Terminal.Gui/Views/FileDialogs/FileDialog.cd

@@ -1,6 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
 <ClassDiagram MajorVersion="1" MinorVersion="1">
-  <Class Name="Terminal.Gui.FileDialog">
+  <Class Name="Terminal.Gui.FileServices.DefaultSearchMatcher" Collapsed="true" BaseTypeListCollapsed="true">
+    <Position X="9.25" Y="6" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAACAAAAAAgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>FileServices\DefaultSearchMatcher.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.FileServices.FileSystemInfoStats">
+    <Position X="14" Y="0.5" Width="2.5" />
+    <TypeIdentifier>
+      <HashCode>ABAIQAIIIAAAAAACQAAAAIQAAAQAAIAAAQAAAAAIAAI=</HashCode>
+      <FileName>FileServices\FileSystemInfoStats.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.Views.FileDialog">
     <Position X="0.5" Y="0.5" Width="2.75" />
     <Compartments>
       <Compartment Name="Fields" Collapsed="true" />
@@ -8,190 +23,146 @@
       <Compartment Name="Nested Types" Collapsed="false" />
     </Compartments>
     <NestedTypes>
-      <Class Name="Terminal.Gui.FileDialog.SearchState" Collapsed="true">
-        <TypeIdentifier>
-          <NewMemberFileName>Windows\FileDialog.cs</NewMemberFileName>
-        </TypeIdentifier>
-      </Class>
-      <Class Name="Terminal.Gui.FileDialog.FileDialogCollectionNavigator" Collapsed="true">
+      <Class Name="Terminal.Gui.Views.FileDialog.SearchState" Collapsed="true">
         <TypeIdentifier>
-          <NewMemberFileName>Views\FileDialog.cs</NewMemberFileName>
+          <NewMemberFileName>Views\FileDialogs\FileDialog.cs</NewMemberFileName>
         </TypeIdentifier>
       </Class>
     </NestedTypes>
     <TypeIdentifier>
-      <HashCode>g4YYDAEXEDKZgHMFyFAikQCFSKUQhRDABqJIlBSAwgw=</HashCode>
-      <FileName>Views\FileDialog.cs</FileName>
-    </TypeIdentifier>
-    <ShowAsAssociation>
-      <Field Name="history" />
-      <Property Name="Style" />
-      <Property Name="OpenMode" />
-    </ShowAsAssociation>
-    <ShowAsCollectionAssociation>
-      <Property Name="AllowedTypes" />
-    </ShowAsCollectionAssociation>
-  </Class>
-  <Class Name="Terminal.Gui.AllowedTypeAny" BaseTypeListCollapsed="true">
-    <Position X="11.75" Y="5.75" Width="1.5" />
-    <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAABAA=</HashCode>
-      <FileName>FileServices\AllowedType.cs</FileName>
+      <HashCode>iIY4LQFUHDKVIHIESBoigQcFT6GxhBDABGJItBQAwAQ=</HashCode>
+      <FileName>Views\FileDialogs\FileDialog.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.AllowedType" BaseTypeListCollapsed="true">
-    <Position X="13.25" Y="5.75" Width="1.5" />
-    <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAgAAAEAAAAAAAAAAAAAAAAAAgAAAAABAA=</HashCode>
-      <FileName>FileServices\AllowedType.cs</FileName>
-    </TypeIdentifier>
-    <Lollipop Position="0.2" />
-  </Class>
-  <Class Name="Terminal.Gui.DefaultFileOperations" Collapsed="true" BaseTypeListCollapsed="true">
-    <Position X="7" Y="6" Width="2" />
+  <Class Name="Terminal.Gui.Views.FileDialogStyle">
+    <Position X="3.5" Y="0.5" Width="2.75" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAACAAAAAAACAAAAEAAAAAAAAAAAAAgAA=</HashCode>
-      <FileName>FileServices\DefaultFileOperations.cs</FileName>
+      <HashCode>GgBAAAFHAAAAuAAAAAAAEAQQBYAAKREAAAAYQCCAAAA=</HashCode>
+      <FileName>Views\FileDialogs\FileDialogStyle.cs</FileName>
     </TypeIdentifier>
-    <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.DefaultSearchMatcher" Collapsed="true" BaseTypeListCollapsed="true">
-    <Position X="9.25" Y="6" Width="2" />
+  <Class Name="Terminal.Gui.Views.FilesSelectedEventArgs">
+    <Position X="6.5" Y="0.5" Width="2" />
+    <Compartments>
+      <Compartment Name="Methods" Collapsed="true" />
+    </Compartments>
     <TypeIdentifier>
-      <HashCode>AAACAAAAAAgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>FileServices\DefaultSearchMatcher.cs</FileName>
+      <HashCode>AAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAA=</HashCode>
+      <FileName>Views\FileDialogs\FilesSelectedEventArgs.cs</FileName>
     </TypeIdentifier>
-    <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.FileDialogHistory">
+  <Class Name="Terminal.Gui.Views.FileDialogHistory">
     <Position X="9.25" Y="0.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AQABAgEAAAAAAAAAIACAAAAAAAAAAQAAAAAAAAAADAI=</HashCode>
-      <FileName>FileServices\FileDialogHistory.cs</FileName>
+      <FileName>Views\FileDialogs\FileDialogHistory.cs</FileName>
     </TypeIdentifier>
-    <ShowAsCollectionAssociation>
-      <Field Name="back" />
-    </ShowAsCollectionAssociation>
   </Class>
-  <Class Name="Terminal.Gui.FileDialogRootTreeNode" Collapsed="true">
-    <Position X="2.5" Y="9" Width="2" />
-    <TypeIdentifier>
-      <HashCode>AAAAEAAAAAAAAAIEAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>FileServices\FileDialogRootTreeNode.cs</FileName>
-    </TypeIdentifier>
-  </Class>
-  <Class Name="Terminal.Gui.FileDialogState">
+  <Class Name="Terminal.Gui.Views.FileDialogState">
     <Position X="11.5" Y="0.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AABAABAAAAAAAAIAAAAEQAAAAAAAQAAAAgAAAAAAAAI=</HashCode>
-      <FileName>FileServices\FileDialogState.cs</FileName>
+      <FileName>Views\FileDialogs\FileDialogState.cs</FileName>
     </TypeIdentifier>
     <ShowAsCollectionAssociation>
       <Property Name="Children" />
     </ShowAsCollectionAssociation>
   </Class>
-  <Class Name="Terminal.Gui.FileDialogStyle">
-    <Position X="3.5" Y="0.5" Width="2.75" />
+  <Class Name="Terminal.Gui.Views.AllowedType" BaseTypeListCollapsed="true">
+    <Position X="13.25" Y="5.75" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>GgBIAAFEAAAAuAAAAgAEEASABQACKRkAAAEYACCAAAA=</HashCode>
-      <FileName>FileServices\FileDialogStyle.cs</FileName>
+      <HashCode>AAAAAAAAAAAgAAAEAAAAAAAAAAAAAAAAAAgAAAAABAA=</HashCode>
+      <FileName>Views\FileDialogs\AllowedType.cs</FileName>
     </TypeIdentifier>
+    <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.FileDialogTreeBuilder" BaseTypeListCollapsed="true">
-    <Position X="0.5" Y="6.75" Width="1.75" />
+  <Class Name="Terminal.Gui.Views.AllowedTypeAny" BaseTypeListCollapsed="true">
+    <Position X="11.75" Y="5.75" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>EAAACAAAAAAAAAAAQAAAAAQAAAAAQAAAAAAAAAQACAA=</HashCode>
-      <FileName>FileServices\FileDialogTreeBuilder.cs</FileName>
+      <HashCode>AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAABAA=</HashCode>
+      <FileName>Views\FileDialogs\AllowedType.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.FilesSelectedEventArgs">
-    <Position X="6.5" Y="0.5" Width="2" />
+  <Class Name="Terminal.Gui.Text.NerdFonts">
+    <Position X="9.25" Y="6.75" Width="2" />
     <Compartments>
-      <Compartment Name="Methods" Collapsed="true" />
+      <Compartment Name="Fields" Collapsed="true" />
     </Compartments>
     <TypeIdentifier>
-      <HashCode>AAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAA=</HashCode>
-      <FileName>FileServices\FilesSelectedEventArgs.cs</FileName>
+      <HashCode>AIACAAABQAAAAAAAAAAACAAAIACAAAAAAAIAAAAAAAA=</HashCode>
+      <FileName>Text\NerdFonts.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.FileSystemInfoStats">
-    <Position X="14" Y="0.5" Width="2.5" />
+  <Class Name="Terminal.Gui.FileServices.FileSystemTreeBuilder" BaseTypeListCollapsed="true">
+    <Position X="0.5" Y="6.75" Width="1.75" />
     <TypeIdentifier>
-      <HashCode>ABAIQAIIIAAAAAACQAAAAIQAAAQAAIAAAQAAAAAIAAI=</HashCode>
-      <FileName>FileServices\FileSystemInfoStats.cs</FileName>
+      <HashCode>EAAAAAAAAAAAAAAAAAAABAAwAAAAQAAAAABAAAAACAA=</HashCode>
+      <FileName>FileServices\FileSystemTreeBuilder.cs</FileName>
     </TypeIdentifier>
+    <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.NerdFonts">
-    <Position X="9.25" Y="6.75" Width="2" />
-    <Compartments>
-      <Compartment Name="Fields" Collapsed="true" />
-    </Compartments>
+  <Class Name="Terminal.Gui.Views.DefaultFileOperations" BaseTypeListCollapsed="true">
+    <Position X="7" Y="6" Width="2" />
     <TypeIdentifier>
-      <HashCode>AIACAAABQAAAAAAAAAAAAAAAIACAAAAAAAIAAAQAAAA=</HashCode>
-      <FileName>Views\NerdFonts.cs</FileName>
+      <HashCode>AAAAAAAAAAAAACAAAAAAACAAAAEAAAAAAAAAAAAAgAA=</HashCode>
+      <FileName>Views\FileDialogs\DefaultFileOperations.cs</FileName>
     </TypeIdentifier>
+    <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.FileDialogIconGetterArgs" Collapsed="true">
-    <Position X="6.75" Y="6.75" Width="2" />
+  <Class Name="Terminal.Gui.FileServices.FileSystemColorProvider" Collapsed="true">
+    <Position X="7" Y="8.5" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAgAAAAAAAAAQAAAAAAAEAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>FileServices\FileDialogIconGetterArgs.cs</FileName>
+      <HashCode>AgAAAAAAAEAAAAAAAAAAAAEAAAAAAACAAAAAAAAAAAA=</HashCode>
+      <FileName>FileServices\FileSystemColorProvider.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.FileDialogTableSource" Collapsed="true">
-    <Position X="4.75" Y="9" Width="2" />
+  <Class Name="Terminal.Gui.FileServices.FileSystemIconProvider" Collapsed="true">
+    <Position X="7" Y="7.75" Width="2" />
+    <TypeIdentifier>
+      <HashCode>ABAAAAAAAACAQAAAAAAAAEAgAAAAAQAAAAAAAAAAAiA=</HashCode>
+      <FileName>FileServices\FileSystemIconProvider.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.Views.FileDialogTableSource">
+    <Position X="2.5" Y="8" Width="2.5" />
     <Compartments>
       <Compartment Name="Fields" Collapsed="true" />
     </Compartments>
     <TypeIdentifier>
       <HashCode>AQAAAAAAIAACAEAACAAAAAACAAAEAAAEAAAAgAgBBAA=</HashCode>
-      <FileName>Views\FileDialogTableSource.cs</FileName>
+      <FileName>Views\FileDialogs\FileDialogTableSource.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Interface Name="Terminal.Gui.IAllowedType" Collapsed="true">
-    <Position X="12" Y="4.25" Width="1.5" />
-    <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAA=</HashCode>
-      <FileName>FileServices\AllowedType.cs</FileName>
-    </TypeIdentifier>
-  </Interface>
-  <Interface Name="Terminal.Gui.IFileOperations">
+  <Interface Name="Terminal.Gui.FileServices.IFileOperations">
     <Position X="7" Y="4.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAACAAAAAAAAAAAAEAAAAAAAAAAAAAgAA=</HashCode>
       <FileName>FileServices\IFileOperations.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.ISearchMatcher">
+  <Interface Name="Terminal.Gui.FileServices.ISearchMatcher">
     <Position X="9.25" Y="4.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>FileServices\ISearchMatcher.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Enum Name="Terminal.Gui.OpenMode">
-    <Position X="7.5" Y="2.5" Width="1.5" />
+  <Interface Name="Terminal.Gui.Views.IAllowedType" Collapsed="true">
+    <Position X="12" Y="4.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAABAAAAAAACAAAAAAAAAAAAAEAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>Views\OpenDialog.cs</FileName>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAA=</HashCode>
+      <FileName>Views\FileDialogs\AllowedType.cs</FileName>
     </TypeIdentifier>
-  </Enum>
-  <Enum Name="Terminal.Gui.FileDialogIconGetterContext">
-    <Position X="6.75" Y="7.25" Width="2.25" />
+  </Interface>
+  <Enum Name="Terminal.Gui.Views.OpenMode">
+    <Position X="7.5" Y="2.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>FileServices\FileDialogIconGetterContext.cs</FileName>
+      <HashCode>AAAAABAAAAAAACAAAAAAAAAAAAAEAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>Views\FileDialogs\OpenMode.cs</FileName>
     </TypeIdentifier>
   </Enum>
-  <Delegate Name="Terminal.Gui.FileDialogTreeRootGetter" Collapsed="true">
-    <Position X="2.5" Y="8.25" Width="2" />
-    <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAA=</HashCode>
-      <FileName>FileServices\FileDialogRootTreeNode.cs</FileName>
-    </TypeIdentifier>
-  </Delegate>
   <Font Name="Segoe UI" Size="9" />
 </ClassDiagram>

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

@@ -19,7 +19,7 @@ internal sealed class Menu : View
         }
 
         Application.MouseEvent += Application_RootMouseEvent;
-        Application.UnGrabbedMouse += Application_UnGrabbedMouse;
+        Application.MouseGrabHandler.UnGrabbedMouse += Application_UnGrabbedMouse;
 
         // Things this view knows how to do
         AddCommand (Command.Up, () => MoveUp ());
@@ -220,7 +220,7 @@ internal sealed class Menu : View
             return;
         }
 
-        Application.UngrabMouse ();
+        Application.MouseGrabHandler.UngrabMouse ();
         _host.CloseAllMenus ();
         Application.LayoutAndDraw (true);
 
@@ -238,7 +238,7 @@ internal sealed class Menu : View
         }
 
         Application.MouseEvent -= Application_RootMouseEvent;
-        Application.UnGrabbedMouse -= Application_UnGrabbedMouse;
+        Application.MouseGrabHandler.UnGrabbedMouse -= Application_UnGrabbedMouse;
         base.Dispose (disposing);
     }
 
@@ -535,7 +535,7 @@ internal sealed class Menu : View
 
     private void CloseAllMenus ()
     {
-        Application.UngrabMouse ();
+        Application.MouseGrabHandler.UngrabMouse ();
         _host.CloseAllMenus ();
     }
 

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

@@ -442,12 +442,12 @@ public class MenuBar : View, IDesignable
 
         if (_isContextMenuLoading)
         {
-            Application.GrabMouse (_openMenu);
+            Application.MouseGrabHandler.GrabMouse (_openMenu);
             _isContextMenuLoading = false;
         }
         else
         {
-            Application.GrabMouse (this);
+            Application.MouseGrabHandler.GrabMouse (this);
         }
     }
 
@@ -524,16 +524,16 @@ public class MenuBar : View, IDesignable
 
         SetNeedsDraw ();
 
-        if (Application.MouseGrabView is { } && Application.MouseGrabView is MenuBar && Application.MouseGrabView != this)
+        if (Application.MouseGrabHandler.MouseGrabView is { } && Application.MouseGrabHandler.MouseGrabView is MenuBar && Application.MouseGrabHandler.MouseGrabView != this)
         {
-            var menuBar = Application.MouseGrabView as MenuBar;
+            var menuBar = Application.MouseGrabHandler.MouseGrabView as MenuBar;
 
             if (menuBar!.IsMenuOpen)
             {
                 menuBar.CleanUp ();
             }
         }
-        Application.UngrabMouse ();
+        Application.MouseGrabHandler.UngrabMouse ();
         _isCleaning = false;
     }
 
@@ -556,7 +556,7 @@ public class MenuBar : View, IDesignable
                 _selected = -1;
             }
 
-            Application.UngrabMouse ();
+            Application.MouseGrabHandler.UngrabMouse ();
         }
 
         if (OpenCurrentMenu is { })
@@ -622,9 +622,9 @@ public class MenuBar : View, IDesignable
                     _previousFocused.SetFocus ();
                 }
 
-                if (Application.MouseGrabView == _openMenu)
+                if (Application.MouseGrabHandler.MouseGrabView == _openMenu)
                 {
-                    Application.UngrabMouse ();
+                    Application.MouseGrabHandler.UngrabMouse ();
                 }
                 _openMenu?.Dispose ();
                 _openMenu = null;
@@ -652,9 +652,9 @@ public class MenuBar : View, IDesignable
                     if (OpenCurrentMenu is { })
                     {
                         SuperView?.Remove (OpenCurrentMenu);
-                        if (Application.MouseGrabView == OpenCurrentMenu)
+                        if (Application.MouseGrabHandler.MouseGrabView == OpenCurrentMenu)
                         {
-                            Application.UngrabMouse ();
+                            Application.MouseGrabHandler.UngrabMouse ();
                         }
                         OpenCurrentMenu.Dispose ();
                         OpenCurrentMenu = null;
@@ -845,9 +845,9 @@ public class MenuBar : View, IDesignable
                 if (_openMenu is { })
                 {
                     SuperView?.Remove (_openMenu);
-                    if (Application.MouseGrabView == _openMenu)
+                    if (Application.MouseGrabHandler.MouseGrabView == _openMenu)
                     {
-                        Application.UngrabMouse ();
+                        Application.MouseGrabHandler.UngrabMouse ();
                     }
                     _openMenu.Dispose ();
                     _openMenu = null;
@@ -935,7 +935,7 @@ public class MenuBar : View, IDesignable
                             Host = this, X = first!.Frame.Left, Y = first.Frame.Top, BarItems = newSubMenu
                         };
                         last!.Visible = false;
-                        Application.GrabMouse (OpenCurrentMenu);
+                        Application.MouseGrabHandler.GrabMouse (OpenCurrentMenu);
                     }
 
                     OpenCurrentMenu._previousSubFocused = last._previousSubFocused;
@@ -1029,9 +1029,9 @@ public class MenuBar : View, IDesignable
             foreach (Menu item in _openSubMenu)
             {
                 SuperView?.Remove (item);
-                if (Application.MouseGrabView == item)
+                if (Application.MouseGrabHandler.MouseGrabView == item)
                 {
-                    Application.UngrabMouse ();
+                    Application.MouseGrabHandler.UngrabMouse ();
                 }
                 item.Dispose ();
             }
@@ -1045,7 +1045,7 @@ public class MenuBar : View, IDesignable
             return false;
         }
 
-        Application.AddIdle (
+        Application.AddTimeout (TimeSpan.Zero,
                                        () =>
                                        {
                                            action ();
@@ -1137,7 +1137,7 @@ public class MenuBar : View, IDesignable
             return false;
         }
 
-        Application.UngrabMouse ();
+        Application.MouseGrabHandler.UngrabMouse ();
         CloseAllMenus ();
         Application.LayoutAndDraw (true);
         _openedByAltKey = true;
@@ -1209,15 +1209,15 @@ public class MenuBar : View, IDesignable
             Point screen = ViewportToScreen (new Point (0, i));
             var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = mi };
             menu.Run (mi.Action);
-            if (Application.MouseGrabView == menu)
+            if (Application.MouseGrabHandler.MouseGrabView == menu)
             {
-                Application.UngrabMouse ();
+                Application.MouseGrabHandler.UngrabMouse ();
             }
             menu.Dispose ();
         }
         else
         {
-            Application.GrabMouse (this);
+            Application.MouseGrabHandler.GrabMouse (this);
             _selected = i;
             OpenMenu (i);
 
@@ -1280,9 +1280,9 @@ public class MenuBar : View, IDesignable
                 SuperView!.Remove (menu);
                 _openSubMenu.Remove (menu);
 
-                if (Application.MouseGrabView == menu)
+                if (Application.MouseGrabHandler.MouseGrabView == menu)
                 {
-                    Application.GrabMouse (this);
+                    Application.MouseGrabHandler.GrabMouse (this);
                 }
 
                 menu.Dispose ();
@@ -1458,9 +1458,9 @@ public class MenuBar : View, IDesignable
                             Point screen = ViewportToScreen (new Point (0, i));
                             var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = Menus [i] };
                             menu.Run (Menus [i].Action);
-                            if (Application.MouseGrabView == menu)
+                            if (Application.MouseGrabHandler.MouseGrabView == menu)
                             {
-                                Application.UngrabMouse ();
+                                Application.MouseGrabHandler.UngrabMouse ();
                             }
 
                             menu.Dispose ();
@@ -1535,7 +1535,7 @@ public class MenuBar : View, IDesignable
 
     internal bool HandleGrabView (MouseEventArgs me, View current)
     {
-        if (Application.MouseGrabView is { })
+        if (Application.MouseGrabHandler.MouseGrabView is { })
         {
             if (me.View is MenuBar or Menu)
             {
@@ -1546,7 +1546,7 @@ public class MenuBar : View, IDesignable
                     if (me.Flags == MouseFlags.Button1Clicked)
                     {
                         mbar.CleanUp ();
-                        Application.GrabMouse (me.View);
+                        Application.MouseGrabHandler.GrabMouse (me.View);
                     }
                     else
                     {
@@ -1556,10 +1556,10 @@ public class MenuBar : View, IDesignable
                     }
                 }
 
-                if (Application.MouseGrabView != me.View)
+                if (Application.MouseGrabHandler.MouseGrabView != me.View)
                 {
                     View v = me.View;
-                    Application.GrabMouse (v);
+                    Application.MouseGrabHandler.GrabMouse (v);
 
                     return true;
                 }
@@ -1567,7 +1567,7 @@ public class MenuBar : View, IDesignable
                 if (me.View != current)
                 {
                     View v = me.View;
-                    Application.GrabMouse (v);
+                    Application.MouseGrabHandler.GrabMouse (v);
                     MouseEventArgs nme;
 
                     if (me.Position.Y > -1)
@@ -1599,7 +1599,7 @@ public class MenuBar : View, IDesignable
                      && me.Flags != MouseFlags.ReportMousePosition
                      && me.Flags != 0)
             {
-                Application.UngrabMouse ();
+                Application.MouseGrabHandler.UngrabMouse ();
 
                 if (IsMenuOpen)
                 {
@@ -1625,11 +1625,11 @@ public class MenuBar : View, IDesignable
                                           MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
                                          )))
         {
-            Application.GrabMouse (current);
+            Application.MouseGrabHandler.GrabMouse (current);
         }
         else if (IsMenuOpen && (me.View is MenuBar || me.View is Menu))
         {
-            Application.GrabMouse (me.View);
+            Application.MouseGrabHandler.GrabMouse (me.View);
         }
         else
         {
@@ -1645,7 +1645,7 @@ public class MenuBar : View, IDesignable
 
     private MenuBar? GetMouseGrabViewInstance (View? view)
     {
-        if (view is null || Application.MouseGrabView is null)
+        if (view is null || Application.MouseGrabHandler.MouseGrabView is null)
         {
             return null;
         }
@@ -1661,7 +1661,7 @@ public class MenuBar : View, IDesignable
             hostView = ((Menu)view).Host;
         }
 
-        View grabView = Application.MouseGrabView;
+        View grabView = Application.MouseGrabHandler.MouseGrabView;
         MenuBar? hostGrabView = null;
 
         if (grabView is MenuBar bar)

+ 4 - 4
Terminal.Gui/Views/ScrollBar/ScrollSlider.cs

@@ -307,9 +307,9 @@ public class ScrollSlider : View, IOrientation, IDesignable
         {
             if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && _lastLocation == -1)
             {
-                if (Application.MouseGrabView != this)
+                if (Application.MouseGrabHandler.MouseGrabView != this)
                 {
-                    Application.GrabMouse (this);
+                    Application.MouseGrabHandler.GrabMouse (this);
                     _lastLocation = location;
                 }
             }
@@ -333,9 +333,9 @@ public class ScrollSlider : View, IOrientation, IDesignable
             {
                 _lastLocation = -1;
 
-                if (Application.MouseGrabView == this)
+                if (Application.MouseGrabHandler.MouseGrabView == this)
                 {
-                    Application.UngrabMouse ();
+                    Application.MouseGrabHandler.UngrabMouse ();
                 }
             }
 

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

@@ -1311,7 +1311,7 @@ public class Slider<T> : View, IOrientation
             {
                 _dragPosition = mouseEvent.Position;
                 _moveRenderPosition = ClampMovePosition ((Point)_dragPosition);
-                Application.GrabMouse (this);
+                Application.MouseGrabHandler.GrabMouse (this);
             }
 
             SetNeedsDraw ();
@@ -1357,7 +1357,7 @@ public class Slider<T> : View, IOrientation
             || mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))
         {
             // End Drag
-            Application.UngrabMouse ();
+            Application.MouseGrabHandler.UngrabMouse ();
             _dragPosition = null;
             _moveRenderPosition = null;
 

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

@@ -855,16 +855,16 @@ public class TextField : View, IDesignable
             _isButtonReleased = false;
             PrepareSelection (x);
 
-            if (Application.MouseGrabView is null)
+            if (Application.MouseGrabHandler.MouseGrabView is null)
             {
-                Application.GrabMouse (this);
+                Application.MouseGrabHandler.GrabMouse (this);
             }
         }
         else if (ev.Flags == MouseFlags.Button1Released)
         {
             _isButtonReleased = true;
             _isButtonPressed = false;
-            Application.UngrabMouse ();
+            Application.MouseGrabHandler.UngrabMouse ();
         }
         else if (ev.Flags == MouseFlags.Button1DoubleClicked)
         {
@@ -1007,12 +1007,12 @@ public class TextField : View, IDesignable
     /// <inheritdoc/>
     protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view)
     {
-        if (Application.MouseGrabView is { } && Application.MouseGrabView == this)
+        if (Application.MouseGrabHandler.MouseGrabView is { } && Application.MouseGrabHandler.MouseGrabView == this)
         {
-            Application.UngrabMouse ();
+            Application.MouseGrabHandler.UngrabMouse ();
         }
 
-        //if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar))
+        //if (SelectedLength != 0 && !(Application.MouseGrabHandler.MouseGrabView is MenuBar))
         //	ClearAllSelection ();
     }
 

+ 6 - 6
Terminal.Gui/Views/TextInput/TextView.cs

@@ -1677,15 +1677,15 @@ public class TextView : View, IDesignable
             _lastWasKill = false;
             _columnTrack = CurrentColumn;
 
-            if (Application.MouseGrabView is null)
+            if (Application.MouseGrabHandler.MouseGrabView is null)
             {
-                Application.GrabMouse (this);
+                Application.MouseGrabHandler.GrabMouse (this);
             }
         }
         else if (ev.Flags.HasFlag (MouseFlags.Button1Released))
         {
             _isButtonReleased = true;
-            Application.UngrabMouse ();
+            Application.MouseGrabHandler.UngrabMouse ();
         }
         else if (ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked))
         {
@@ -1893,9 +1893,9 @@ public class TextView : View, IDesignable
     /// <inheritdoc/>
     protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? view)
     {
-        if (Application.MouseGrabView is { } && Application.MouseGrabView == this)
+        if (Application.MouseGrabHandler.MouseGrabView is { } && Application.MouseGrabHandler.MouseGrabView == this)
         {
-            Application.UngrabMouse ();
+            Application.MouseGrabHandler.UngrabMouse ();
         }
     }
 
@@ -2039,7 +2039,7 @@ public class TextView : View, IDesignable
             return null;
         }
 
-        if (Application.MouseGrabView == this && IsSelecting)
+        if (Application.MouseGrabHandler.MouseGrabView == this && IsSelecting)
         {
             // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
             //var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Viewport.Height);

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

@@ -916,7 +916,7 @@ public class TileView : View
                 {
                     dragPosition = mouseEvent.Position;
                     dragOrignalPos = Orientation == Orientation.Horizontal ? Y : X;
-                    Application.GrabMouse (this);
+                    Application.MouseGrabHandler.GrabMouse (this);
 
                     if (Orientation == Orientation.Horizontal)
                     { }
@@ -960,7 +960,7 @@ public class TileView : View
             {
                 // End Drag
 
-                Application.UngrabMouse ();
+                Application.MouseGrabHandler.UngrabMouse ();
 
                 //Driver.UncookMouse ();
                 FinalisePosition (

+ 1 - 1
Tests/TerminalGuiFluentTesting/GuiTestContext.cs

@@ -88,7 +88,7 @@ public class GuiTestContext : IDisposable
         }
 
         // Wait for booting to complete with a timeout to avoid hangs
-        if (!booting.WaitAsync (TimeSpan.FromSeconds (5)).Result)
+        if (!booting.WaitAsync (TimeSpan.FromSeconds (10)).Result)
         {
             throw new TimeoutException ("Application failed to start within the allotted time.");
         }

+ 27 - 18
Tests/UnitTests/Application/ApplicationTests.cs

@@ -307,8 +307,7 @@ public class ApplicationTests
 
             // Public Properties
             Assert.Null (Application.Top);
-            Assert.Null (Application.MouseGrabView);
-            Assert.Null (Application.WantContinuousButtonPressedView);
+            Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
             // Don't check Application.ForceDriver
             // Assert.Empty (Application.ForceDriver);
@@ -569,8 +568,7 @@ public class ApplicationTests
         Assert.Null (Application.Top);
         RunState rs = Application.Begin (new ());
         Assert.Equal (Application.Top, rs.Toplevel);
-        Assert.Null (Application.MouseGrabView); // public
-        Assert.Null (Application.WantContinuousButtonPressedView); // public
+        Assert.Null (Application.MouseGrabHandler.MouseGrabView); // public
         Application.Top!.Dispose ();
     }
 
@@ -952,7 +950,7 @@ public class ApplicationTests
         Assert.Equal (new (0, 0), w.Frame.Location);
 
         Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
-        Assert.Equal (w.Border, Application.MouseGrabView);
+        Assert.Equal (w.Border, Application.MouseGrabHandler.MouseGrabView);
         Assert.Equal (new (0, 0), w.Frame.Location);
 
         // Move down and to the right.
@@ -1116,6 +1114,8 @@ public class ApplicationTests
 
     private class TestToplevel : Toplevel { }
 
+    private readonly object _forceDriverLock = new ();
+
     [Theory]
     [InlineData ("v2win", typeof (ConsoleDriverFacade<WindowsConsole.InputRecord>))]
     [InlineData ("v2net", typeof (ConsoleDriverFacade<ConsoleKeyInfo>))]
@@ -1129,20 +1129,29 @@ public class ApplicationTests
 
         var result = false;
 
-        Task.Run (() =>
-                  {
-                      Task.Delay (300).Wait ();
-                  }).ContinueWith (
-                                   (t, _) =>
+        lock (_forceDriverLock)
+        {
+            Task.Run (() =>
+            {
+                while (!Application.Initialized)
+                {
+                    Task.Delay (300).Wait ();
+                }
+            })
+                .ContinueWith (
+                               (t, _) =>
+                               {
+                                   // no longer loading
+                                   Assert.True (Application.Initialized);
+
+                                   Application.Invoke (() =>
                                    {
-                                       // no longer loading
-                                       Application.Invoke (() =>
-                                                           {
-                                                               result = true;
-                                                               Application.RequestStop ();
-                                                           });
-                                   },
-                                   TaskScheduler.FromCurrentSynchronizationContext ());
+                                       result = true;
+                                       Application.RequestStop ();
+                                   });
+                               },
+                               TaskScheduler.FromCurrentSynchronizationContext ());
+        }
 
         Application.ForceDriver = driverName;
         Application.Run<TestToplevel> ();

+ 79 - 101
Tests/UnitTests/Application/MainLoopTests.cs

@@ -29,61 +29,38 @@ public class MainLoopTests
 
     // Idle Handler tests
     [Fact]
-    public void AddIdle_Adds_And_Removes ()
+    public void AddTimeout_Adds_And_Removes ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
 
         Func<bool> fnTrue = () => true;
         Func<bool> fnFalse = () => false;
 
-        ml.AddIdle (fnTrue);
-        ml.AddIdle (fnFalse);
+        var a = ml.TimedEvents.Add (TimeSpan.Zero, fnTrue);
+        var b = ml.TimedEvents.Add (TimeSpan.Zero, fnFalse);
 
-        Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
-        Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]);
-        Assert.NotEqual (fnFalse, ml.TimedEvents.IdleHandlers [0]);
+        Assert.Equal (2, ml.TimedEvents.Timeouts.Count);
+        Assert.Equal (fnTrue, ml.TimedEvents.Timeouts.ElementAt (0).Value.Callback);
+        Assert.NotEqual (fnFalse, ml.TimedEvents.Timeouts.ElementAt (0).Value.Callback);
 
-        Assert.True (ml.TimedEvents.RemoveIdle (fnTrue));
-        Assert.Single (ml.TimedEvents.IdleHandlers);
+        Assert.True (ml.TimedEvents.Remove (a));
+        Assert.Single (ml.TimedEvents.Timeouts);
 
         // BUGBUG: This doesn't throw or indicate an error. Ideally RemoveIdle would either 
         // throw an exception in this case, or return an error.
         // No. Only need to return a boolean.
-        Assert.False (ml.TimedEvents.RemoveIdle (fnTrue));
+        Assert.False (ml.TimedEvents.Remove (a));
 
-        Assert.True (ml.TimedEvents.RemoveIdle (fnFalse));
+        Assert.True (ml.TimedEvents.Remove (b));
 
         // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either 
         // throw an exception in this case, or return an error.
         // No. Only need to return a boolean.
-        Assert.False (ml.TimedEvents.RemoveIdle (fnFalse));
-
-        // Add again, but with dupe
-        ml.AddIdle (fnTrue);
-        ml.AddIdle (fnTrue);
-
-        Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
-        Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]);
-        Assert.True (ml.TimedEvents.IdleHandlers [0] ());
-        Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [1]);
-        Assert.True (ml.TimedEvents.IdleHandlers [1] ());
-
-        Assert.True (ml.TimedEvents.RemoveIdle (fnTrue));
-        Assert.Single (ml.TimedEvents.IdleHandlers);
-        Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]);
-        Assert.NotEqual (fnFalse, ml.TimedEvents.IdleHandlers [0]);
-
-        Assert.True (ml.TimedEvents.RemoveIdle (fnTrue));
-        Assert.Empty (ml.TimedEvents.IdleHandlers);
-
-        // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either 
-        // throw an exception in this case, or return an error.
-        // No. Only need to return a boolean.
-        Assert.False (ml.TimedEvents.RemoveIdle (fnTrue));
+        Assert.False (ml.TimedEvents.Remove(b));
     }
 
     [Fact]
-    public void AddIdle_Function_GetsCalled_OnIteration ()
+    public void AddTimeout_Function_GetsCalled_OnIteration ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
 
@@ -96,13 +73,13 @@ public class MainLoopTests
                             return true;
                         };
 
-        ml.AddIdle (fn);
+        ml.TimedEvents.Add (TimeSpan.Zero, fn);
         ml.RunIteration ();
         Assert.Equal (1, functionCalled);
     }
 
     [Fact]
-    public void AddIdle_Twice_Returns_False_Called_Twice ()
+    public void AddTimeout_Twice_Returns_False_Called_Twice ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
 
@@ -130,19 +107,21 @@ public class MainLoopTests
                                 return true;
                             };
 
-        ml.AddIdle (fnStop);
-        ml.AddIdle (fn1);
-        ml.AddIdle (fn1);
+        var a = ml.TimedEvents.Add (TimeSpan.Zero, fnStop);
+        var b = ml.TimedEvents.Add (TimeSpan.Zero, fn1);
         ml.Run ();
-        Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
-        Assert.False (ml.TimedEvents.RemoveIdle (fn1));
-        Assert.False (ml.TimedEvents.RemoveIdle (fn1));
 
-        Assert.Equal (2, functionCalled);
+        Assert.True (ml.TimedEvents.Remove(a));
+        Assert.False (ml.TimedEvents.Remove (a));
+
+        // Cannot remove b because it returned false i.e. auto removes itself
+        Assert.False (ml.TimedEvents.Remove (b));
+
+        Assert.Equal (1, functionCalled);
     }
 
     [Fact]
-    public void AddIdleTwice_Function_CalledTwice ()
+    public void AddTimeoutTwice_Function_CalledTwice ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
 
@@ -155,24 +134,24 @@ public class MainLoopTests
                             return true;
                         };
 
-        ml.AddIdle (fn);
-        ml.AddIdle (fn);
+        var a = ml.TimedEvents.Add (TimeSpan.Zero, fn);
+        var b = ml.TimedEvents.Add (TimeSpan.Zero, fn);
         ml.RunIteration ();
         Assert.Equal (2, functionCalled);
-        Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
+        Assert.Equal (2, ml.TimedEvents.Timeouts.Count);
 
         functionCalled = 0;
-        Assert.True (ml.TimedEvents.RemoveIdle (fn));
-        Assert.Single (ml.TimedEvents.IdleHandlers);
+        Assert.True (ml.TimedEvents.Remove (a));
+        Assert.Single (ml.TimedEvents.Timeouts);
         ml.RunIteration ();
         Assert.Equal (1, functionCalled);
 
         functionCalled = 0;
-        Assert.True (ml.TimedEvents.RemoveIdle (fn));
-        Assert.Empty (ml.TimedEvents.IdleHandlers);
+        Assert.True (ml.TimedEvents.Remove (b));
+        Assert.Empty (ml.TimedEvents.Timeouts);
         ml.RunIteration ();
         Assert.Equal (0, functionCalled);
-        Assert.False (ml.TimedEvents.RemoveIdle (fn));
+        Assert.False (ml.TimedEvents.Remove (b));
     }
 
     [Fact]
@@ -189,8 +168,8 @@ public class MainLoopTests
                             return true;
                         };
 
-        ml.AddIdle (fn);
-        Assert.True (ml.TimedEvents.RemoveIdle (fn));
+        var a = ml.TimedEvents.Add (TimeSpan.Zero, fn);
+        Assert.True (ml.TimedEvents.Remove (a));
         ml.RunIteration ();
         Assert.Equal (0, functionCalled);
     }
@@ -211,13 +190,13 @@ public class MainLoopTests
                                   return true;
                               };
 
-        object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
+        object token = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback);
 
-        Assert.True (ml.TimedEvents.RemoveTimeout (token));
+        Assert.True (ml.TimedEvents.Remove (token));
 
         // BUGBUG: This should probably fault?
         // Must return a boolean.
-        Assert.False (ml.TimedEvents.RemoveTimeout (token));
+        Assert.False (ml.TimedEvents.Remove (token));
     }
 
     [Fact]
@@ -241,8 +220,8 @@ public class MainLoopTests
                                   return true;
                               };
 
-        var task1 = new Task (() => token1 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
-        var task2 = new Task (() => token2 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
+        var task1 = new Task (() => token1 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback));
+        var task2 = new Task (() => token2 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback));
         Assert.Null (token1);
         Assert.Null (token2);
         task1.Start ();
@@ -251,8 +230,8 @@ public class MainLoopTests
         Assert.NotNull (token1);
         Assert.NotNull (token2);
         await Task.WhenAll (task1, task2);
-        Assert.True (ml.TimedEvents.RemoveTimeout (token1));
-        Assert.True (ml.TimedEvents.RemoveTimeout (token2));
+        Assert.True (ml.TimedEvents.Remove (token1));
+        Assert.True (ml.TimedEvents.Remove (token2));
 
         Assert.Equal (2, callbackCount);
     }
@@ -278,13 +257,13 @@ public class MainLoopTests
         object sender = null;
         TimeoutEventArgs args = null;
 
-        ml.TimedEvents.TimeoutAdded += (s, e) =>
+        ml.TimedEvents.Added += (s, e) =>
                                        {
                                            sender = s;
                                            args = e;
                                        };
 
-        object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
+        object token = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback);
 
         Assert.Same (ml.TimedEvents, sender);
         Assert.NotNull (args.Timeout);
@@ -313,14 +292,14 @@ public class MainLoopTests
                               };
 
         Parallel.Invoke (
-                         () => token1 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback),
-                         () => token2 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)
+                         () => token1 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback),
+                         () => token2 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback)
                         );
         ml.Run ();
         Assert.NotNull (token1);
         Assert.NotNull (token2);
-        Assert.True (ml.TimedEvents.RemoveTimeout (token1));
-        Assert.True (ml.TimedEvents.RemoveTimeout (token2));
+        Assert.True (ml.TimedEvents.Remove (token1));
+        Assert.True (ml.TimedEvents.Remove (token2));
 
         Assert.Equal (2, callbackCount);
     }
@@ -345,7 +324,7 @@ public class MainLoopTests
 
                                 return true;
                             };
-        ml.AddIdle (fnStop);
+        ml.TimedEvents.Add (TimeSpan.Zero, fnStop);
 
         var callbackCount = 0;
 
@@ -356,8 +335,8 @@ public class MainLoopTests
                                   return true;
                               };
 
-        object token = ml.TimedEvents.AddTimeout (ms, callback);
-        Assert.True (ml.TimedEvents.RemoveTimeout (token));
+        object token = ml.TimedEvents.Add (ms, callback);
+        Assert.True (ml.TimedEvents.Remove (token));
         ml.Run ();
         Assert.Equal (0, callbackCount);
     }
@@ -383,7 +362,7 @@ public class MainLoopTests
 
                                 return true;
                             };
-        ml.AddIdle (fnStop);
+        ml.TimedEvents.Add (TimeSpan.Zero, fnStop);
 
         var callbackCount = 0;
 
@@ -394,11 +373,11 @@ public class MainLoopTests
                                   return false;
                               };
 
-        object token = ml.TimedEvents.AddTimeout (ms, callback);
+        object token = ml.TimedEvents.Add (ms, callback);
         ml.Run ();
         Assert.Equal (1, callbackCount);
         Assert.Equal (10, stopCount);
-        Assert.False (ml.TimedEvents.RemoveTimeout (token));
+        Assert.False (ml.TimedEvents.Remove (token));
     }
 
     [Fact]
@@ -417,9 +396,9 @@ public class MainLoopTests
                                   return true;
                               };
 
-        object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
+        object token = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback);
         ml.Run ();
-        Assert.True (ml.TimedEvents.RemoveTimeout (token));
+        Assert.True (ml.TimedEvents.Remove (token));
 
         Assert.Equal (1, callbackCount);
     }
@@ -442,7 +421,7 @@ public class MainLoopTests
                                   return true;
                               };
 
-        object token = ml.TimedEvents.AddTimeout (ms, callback);
+        object token = ml.TimedEvents.Add (ms, callback);
         watch.Start ();
         ml.Run ();
 
@@ -450,7 +429,7 @@ public class MainLoopTests
         // https://github.com/xunit/assert.xunit/pull/25
         Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
 
-        Assert.True (ml.TimedEvents.RemoveTimeout (token));
+        Assert.True (ml.TimedEvents.Remove (token));
         Assert.Equal (1, callbackCount);
     }
 
@@ -476,7 +455,7 @@ public class MainLoopTests
                                   return true;
                               };
 
-        object token = ml.TimedEvents.AddTimeout (ms, callback);
+        object token = ml.TimedEvents.Add (ms, callback);
         watch.Start ();
         ml.Run ();
 
@@ -484,7 +463,7 @@ public class MainLoopTests
         // https://github.com/xunit/assert.xunit/pull/25
         Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
 
-        Assert.True (ml.TimedEvents.RemoveTimeout (token));
+        Assert.True (ml.TimedEvents.Remove (token));
         Assert.Equal (2, callbackCount);
     }
 
@@ -492,7 +471,7 @@ public class MainLoopTests
     public void CheckTimersAndIdleHandlers_NoTimers_Returns_False ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
-        bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
+        bool retVal = ml.TimedEvents.CheckTimers(out int waitTimeOut);
         Assert.False (retVal);
         Assert.Equal (-1, waitTimeOut);
     }
@@ -503,10 +482,10 @@ public class MainLoopTests
         var ml = new MainLoop (new FakeMainLoop ());
         Func<bool> fnTrue = () => true;
 
-        ml.AddIdle (fnTrue);
-        bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
+        ml.TimedEvents.Add (TimeSpan.Zero, fnTrue);
+        bool retVal = ml.TimedEvents.CheckTimers(out int waitTimeOut);
         Assert.True (retVal);
-        Assert.Equal (-1, waitTimeOut);
+        Assert.Equal (0, waitTimeOut);
     }
 
     [Fact]
@@ -517,8 +496,8 @@ public class MainLoopTests
 
         static bool Callback () { return false; }
 
-        _ = ml.TimedEvents.AddTimeout (ms, Callback);
-        bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
+        _ = ml.TimedEvents.Add (ms, Callback);
+        bool retVal = ml.TimedEvents.CheckTimers (out int waitTimeOut);
 
         Assert.True (retVal);
 
@@ -534,9 +513,9 @@ public class MainLoopTests
 
         static bool Callback () { return false; }
 
-        _ = ml.TimedEvents.AddTimeout (ms, Callback);
-        _ = ml.TimedEvents.AddTimeout (ms, Callback);
-        bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
+        _ = ml.TimedEvents.Add (ms, Callback);
+        _ = ml.TimedEvents.Add (ms, Callback);
+        bool retVal = ml.TimedEvents.CheckTimers (out int waitTimeOut);
 
         Assert.True (retVal);
 
@@ -578,11 +557,11 @@ public class MainLoopTests
                                 return true;
                             };
 
-        ml.AddIdle (fnStop);
-        ml.AddIdle (fn1);
+        var a = ml.TimedEvents.Add (TimeSpan.Zero, fnStop);
+        var b = ml.TimedEvents.Add (TimeSpan.Zero, fn1);
         ml.Run ();
-        Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
-        Assert.False (ml.TimedEvents.RemoveIdle (fn1));
+        Assert.True (ml.TimedEvents.Remove (a));
+        Assert.False (ml.TimedEvents.Remove (a));
 
         Assert.Equal (10, functionCalled);
         Assert.Equal (20, stopCount);
@@ -594,7 +573,6 @@ public class MainLoopTests
         var testMainloop = new TestMainloop ();
         var mainloop = new MainLoop (testMainloop);
         Assert.Empty (mainloop.TimedEvents.Timeouts);
-        Assert.Empty (mainloop.TimedEvents.IdleHandlers);
 
         Assert.NotNull (
                         new App.Timeout { Span = new (), Callback = () => true }
@@ -602,8 +580,8 @@ public class MainLoopTests
     }
 
     [Theory]
-    [MemberData (nameof (TestAddIdle))]
-    public void Mainloop_Invoke_Or_AddIdle_Can_Be_Used_For_Events_Or_Actions (
+    [MemberData (nameof (TestAddTimeout))]
+    public void Mainloop_Invoke_Or_AddTimeout_Can_Be_Used_For_Events_Or_Actions (
         Action action,
         string pclickMe,
         string pcancel,
@@ -683,7 +661,7 @@ public class MainLoopTests
 
         Application.Shutdown ();
     }
-
+    
     [Fact]
     public void RemoveIdle_Function_NotCalled ()
     {
@@ -698,7 +676,7 @@ public class MainLoopTests
                             return true;
                         };
 
-        Assert.False (ml.TimedEvents.RemoveIdle (fn));
+        Assert.False (ml.TimedEvents.Remove ("flibble"));
         ml.RunIteration ();
         Assert.Equal (0, functionCalled);
     }
@@ -722,14 +700,14 @@ public class MainLoopTests
                             return true;
                         };
 
-        ml.AddIdle (fn);
+        var a = ml.TimedEvents.Add (TimeSpan.Zero, fn);
         ml.Run ();
-        Assert.True (ml.TimedEvents.RemoveIdle (fn));
+        Assert.True (ml.TimedEvents.Remove (a));
 
         Assert.Equal (10, functionCalled);
     }
 
-    public static IEnumerable<object []> TestAddIdle
+    public static IEnumerable<object []> TestAddTimeout
     {
         get
         {

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

@@ -260,39 +260,39 @@ public class ApplicationMouseTests
         //                             if (iterations == 0)
         //                             {
         //                                 Assert.True (tf.HasFocus);
-        //                                 Assert.Null (Application.MouseGrabView);
+        //                                 Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
         //                                 Application.RaiseMouseEvent (new () { ScreenPosition = new (5, 5), Flags = MouseFlags.ReportMousePosition });
 
-        //                                 Assert.Equal (sv, Application.MouseGrabView);
+        //                                 Assert.Equal (sv, Application.MouseGrabHandler.MouseGrabView);
 
         //                                 MessageBox.Query ("Title", "Test", "Ok");
 
-        //                                 Assert.Null (Application.MouseGrabView);
+        //                                 Assert.Null (Application.MouseGrabHandler.MouseGrabView);
         //                             }
         //                             else if (iterations == 1)
         //                             {
-        //                                 // Application.MouseGrabView is null because
+        //                                 // Application.MouseGrabHandler.MouseGrabView is null because
         //                                 // another toplevel (Dialog) was opened
-        //                                 Assert.Null (Application.MouseGrabView);
+        //                                 Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
         //                                 Application.RaiseMouseEvent (new () { ScreenPosition = new (5, 5), Flags = MouseFlags.ReportMousePosition });
 
-        //                                 Assert.Null (Application.MouseGrabView);
+        //                                 Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
         //                                 Application.RaiseMouseEvent (new () { ScreenPosition = new (40, 12), Flags = MouseFlags.ReportMousePosition });
 
-        //                                 Assert.Null (Application.MouseGrabView);
+        //                                 Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
         //                                 Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed });
 
-        //                                 Assert.Null (Application.MouseGrabView);
+        //                                 Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
         //                                 Application.RequestStop ();
         //                             }
         //                             else if (iterations == 2)
         //                             {
-        //                                 Assert.Null (Application.MouseGrabView);
+        //                                 Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
         //                                 Application.RequestStop ();
         //                             }
@@ -313,33 +313,33 @@ public class ApplicationMouseTests
         var view2 = new View { Id = "view2" };
         var view3 = new View { Id = "view3" };
 
-        Application.GrabbedMouse += Application_GrabbedMouse;
-        Application.UnGrabbedMouse += Application_UnGrabbedMouse;
+        Application.MouseGrabHandler.GrabbedMouse += Application_GrabbedMouse;
+        Application.MouseGrabHandler.UnGrabbedMouse += Application_UnGrabbedMouse;
 
-        Application.GrabMouse (view1);
+        Application.MouseGrabHandler.GrabMouse (view1);
         Assert.Equal (0, count);
         Assert.Equal (grabView, view1);
-        Assert.Equal (view1, Application.MouseGrabView);
+        Assert.Equal (view1, Application.MouseGrabHandler.MouseGrabView);
 
-        Application.UngrabMouse ();
+        Application.MouseGrabHandler.UngrabMouse ();
         Assert.Equal (1, count);
         Assert.Equal (grabView, view1);
-        Assert.Null (Application.MouseGrabView);
+        Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
-        Application.GrabbedMouse += Application_GrabbedMouse;
-        Application.UnGrabbedMouse += Application_UnGrabbedMouse;
+        Application.MouseGrabHandler.GrabbedMouse += Application_GrabbedMouse;
+        Application.MouseGrabHandler.UnGrabbedMouse += Application_UnGrabbedMouse;
 
-        Application.GrabMouse (view2);
+        Application.MouseGrabHandler.GrabMouse (view2);
         Assert.Equal (1, count);
         Assert.Equal (grabView, view2);
-        Assert.Equal (view2, Application.MouseGrabView);
+        Assert.Equal (view2, Application.MouseGrabHandler.MouseGrabView);
 
-        Application.UngrabMouse ();
+        Application.MouseGrabHandler.UngrabMouse ();
         Assert.Equal (2, count);
         Assert.Equal (grabView, view2);
-        Assert.Equal (view3, Application.MouseGrabView);
-        Application.UngrabMouse ();
-        Assert.Null (Application.MouseGrabView);
+        Assert.Equal (view3, Application.MouseGrabHandler.MouseGrabView);
+        Application.MouseGrabHandler.UngrabMouse ();
+        Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
         void Application_GrabbedMouse (object sender, ViewEventArgs e)
         {
@@ -354,7 +354,7 @@ public class ApplicationMouseTests
                 grabView = view2;
             }
 
-            Application.GrabbedMouse -= Application_GrabbedMouse;
+            Application.MouseGrabHandler.GrabbedMouse -= Application_GrabbedMouse;
         }
 
         void Application_UnGrabbedMouse (object sender, ViewEventArgs e)
@@ -375,10 +375,10 @@ public class ApplicationMouseTests
             if (count > 1)
             {
                 // It's possible to grab another view after the previous was ungrabbed
-                Application.GrabMouse (view3);
+                Application.MouseGrabHandler.GrabMouse (view3);
             }
 
-            Application.UnGrabbedMouse -= Application_UnGrabbedMouse;
+            Application.MouseGrabHandler.UnGrabbedMouse -= Application_UnGrabbedMouse;
         }
     }
 
@@ -393,18 +393,18 @@ public class ApplicationMouseTests
         top.Add (view);
         Application.Begin (top);
 
-        Assert.Null (Application.MouseGrabView);
-        Application.GrabMouse (view);
-        Assert.Equal (view, Application.MouseGrabView);
+        Assert.Null (Application.MouseGrabHandler.MouseGrabView);
+        Application.MouseGrabHandler.GrabMouse (view);
+        Assert.Equal (view, Application.MouseGrabHandler.MouseGrabView);
         top.Remove (view);
-        Application.UngrabMouse ();
+        Application.MouseGrabHandler.UngrabMouse ();
         view.Dispose ();
 #if DEBUG_IDISPOSABLE
         Assert.True (view.WasDisposed);
 #endif
 
         Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed });
-        Assert.Null (Application.MouseGrabView);
+        Assert.Null (Application.MouseGrabHandler.MouseGrabView);
         Assert.Equal (0, count);
         top.Dispose ();
     }

+ 18 - 19
Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs

@@ -15,7 +15,7 @@ public class MainLoopDriverTests
     [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
 
     //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_AddIdle_ValidIdleHandler_ReturnsToken (Type driverType, Type mainLoopDriverType)
+    public void MainLoop_AddTimeout_ValidIdleHandler_ReturnsToken (Type driverType, Type mainLoopDriverType)
     {
         var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
@@ -29,7 +29,7 @@ public class MainLoopDriverTests
             return false;
         }
 
-        Func<bool> token = mainLoop.AddIdle (IdleHandler);
+        var token = mainLoop.TimedEvents.Add(TimeSpan.Zero, IdleHandler);
 
         Assert.NotNull (token);
         Assert.False (idleHandlerInvoked); // Idle handler should not be invoked immediately
@@ -52,7 +52,7 @@ public class MainLoopDriverTests
         var mainLoop = new MainLoop (mainLoopDriver);
         var callbackInvoked = false;
 
-        object token = mainLoop.TimedEvents.AddTimeout (
+        object token = mainLoop.TimedEvents.Add (
                                             TimeSpan.FromMilliseconds (100),
                                             () =>
                                             {
@@ -87,11 +87,11 @@ public class MainLoopDriverTests
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
-        mainLoop.AddIdle (() => false);
-        bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout);
+        mainLoop.TimedEvents.Add (TimeSpan.Zero, () => false);
+        bool result = mainLoop.TimedEvents.CheckTimers (out int waitTimeout);
 
         Assert.True (result);
-        Assert.Equal (-1, waitTimeout);
+        Assert.Equal (0, waitTimeout);
         mainLoop.Dispose ();
     }
 
@@ -102,7 +102,7 @@ public class MainLoopDriverTests
     [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
 
     //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_CheckTimersAndIdleHandlers_NoTimersOrIdleHandlers_ReturnsFalse (
+    public void MainLoop_CheckTimers_NoTimersOrIdleHandlers_ReturnsFalse (
         Type driverType,
         Type mainLoopDriverType
     )
@@ -111,7 +111,7 @@ public class MainLoopDriverTests
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
-        bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout);
+        bool result = mainLoop.TimedEvents.CheckTimers (out int waitTimeout);
 
         Assert.False (result);
         Assert.Equal (-1, waitTimeout);
@@ -134,8 +134,8 @@ public class MainLoopDriverTests
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
-        mainLoop.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
-        bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout);
+        mainLoop.TimedEvents.Add (TimeSpan.FromMilliseconds (100), () => false);
+        bool result = mainLoop.TimedEvents.CheckTimers(out int waitTimeout);
 
         Assert.True (result);
         Assert.True (waitTimeout >= 0);
@@ -158,7 +158,6 @@ public class MainLoopDriverTests
         // Check default values
         Assert.NotNull (mainLoop);
         Assert.Equal (mainLoopDriver, mainLoop.MainLoopDriver);
-        Assert.Empty (mainLoop.TimedEvents.IdleHandlers);
         Assert.Empty (mainLoop.TimedEvents.Timeouts);
         Assert.False (mainLoop.Running);
 
@@ -168,7 +167,6 @@ public class MainLoopDriverTests
         // TODO: It'd be nice if we could really verify IMainLoopDriver.TearDown was called
         // and that it was actually cleaned up.
         Assert.Null (mainLoop.MainLoopDriver);
-        Assert.Empty (mainLoop.TimedEvents.IdleHandlers);
         Assert.Empty (mainLoop.TimedEvents.Timeouts);
         Assert.False (mainLoop.Running);
     }
@@ -186,7 +184,7 @@ public class MainLoopDriverTests
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
-        bool result = mainLoop.TimedEvents.RemoveIdle (() => false);
+        bool result = mainLoop.TimedEvents.Remove("flibble");
 
         Assert.False (result);
         mainLoop.Dispose ();
@@ -207,8 +205,9 @@ public class MainLoopDriverTests
 
         bool IdleHandler () { return false; }
 
-        Func<bool> token = mainLoop.AddIdle (IdleHandler);
-        bool result = mainLoop.TimedEvents.RemoveIdle (token);
+
+        var token = mainLoop.TimedEvents.Add (TimeSpan.Zero, IdleHandler);
+        bool result = mainLoop.TimedEvents.Remove (token);
 
         Assert.True (result);
         mainLoop.Dispose ();
@@ -227,7 +226,7 @@ public class MainLoopDriverTests
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
-        bool result = mainLoop.TimedEvents.RemoveTimeout (new object ());
+        bool result = mainLoop.TimedEvents.Remove (new object ());
 
         Assert.False (result);
     }
@@ -245,8 +244,8 @@ public class MainLoopDriverTests
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
-        object token = mainLoop.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
-        bool result = mainLoop.TimedEvents.RemoveTimeout (token);
+        object token = mainLoop.TimedEvents.Add (TimeSpan.FromMilliseconds (100), () => false);
+        bool result = mainLoop.TimedEvents.Remove (token);
 
         Assert.True (result);
         mainLoop.Dispose ();
@@ -273,7 +272,7 @@ public class MainLoopDriverTests
                                      return false;
                                  };
 
-        mainLoop.AddIdle (idleHandler);
+        mainLoop.TimedEvents.Add (TimeSpan.Zero, idleHandler);
         mainLoop.RunIteration (); // Run an iteration to process the idle handler
 
         Assert.True (idleHandlerInvoked);

+ 2 - 2
Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs

@@ -401,7 +401,7 @@ public class ApplicationV2Tests
 
         v2.Init ();
 
-        v2.AddIdle (IdleExit);
+        v2.AddTimeout (TimeSpan.Zero, IdleExit);
         Assert.Null (Application.Top);
 
         // Blocks until the timeout call is hit
@@ -448,7 +448,7 @@ public class ApplicationV2Tests
                    Assert.Same (t, a.Toplevel);
                };
 
-        v2.AddIdle (IdleExit);
+        v2.AddTimeout(TimeSpan.Zero, IdleExit);
 
         // Blocks until the timeout call is hit
 

+ 3 - 28
Tests/UnitTests/Input/EscSeqUtilsTests.cs

@@ -679,14 +679,7 @@ public class EscSeqUtilsTests
         Assert.Equal (new () { MouseFlags.Button1TripleClicked }, _mouseFlags);
         Assert.Equal (new (1, 2), _pos);
         Assert.False (_isResponse);
-
-        var view = new View { Width = Dim.Fill (), Height = Dim.Fill (), WantContinuousButtonPressed = true };
-        var top = new Toplevel ();
-        top.Add (view);
-        Application.Begin (top);
-
-        Application.RaiseMouseEvent (new () { Position = new (0, 0), Flags = 0 });
-
+        
         ClearAll ();
 
         _cki = new ConsoleKeyInfo []
@@ -734,26 +727,8 @@ public class EscSeqUtilsTests
         Assert.Equal (new (1, 2), _pos);
         Assert.False (_isResponse);
 
-        Application.Iteration += (s, a) =>
-                                 {
-                                     if (_actionStarted)
-                                     {
-                                         // set Application.WantContinuousButtonPressedView to null
-                                         view.WantContinuousButtonPressed = false;
-
-                                         Application.RaiseMouseEvent (new () { Position = new (0, 0), Flags = 0 });
-
-                                         Application.RequestStop ();
-                                     }
-                                 };
-
-        Application.Run (top);
-        top.Dispose ();
-
-        Assert.Null (Application.WantContinuousButtonPressedView);
-
-        Assert.Equal (MouseFlags.Button1Pressed, _arg1);
-        Assert.Equal (new (1, 2), _arg2);
+        Assert.Equal (MouseFlags.None, _arg1);
+        Assert.Equal (new (0, 0), _arg2);
 
         ClearAll ();
 

+ 1 - 1
Tests/UnitTests/View/Adornment/ShadowStyleTests.cs

@@ -160,7 +160,7 @@ public class ShadowStyleTests (ITestOutputHelper output)
         view.NewMouseEvent (new () { Flags = MouseFlags.Button1Released, Position = new (0, 0) });
         Assert.Equal (origThickness, view.Margin.Thickness);
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
         Application.ResetState (true);
     }
 }

+ 71 - 29
Tests/UnitTests/View/Mouse/MouseTests.cs

@@ -1,4 +1,5 @@
-using UnitTests;
+using Moq;
+using UnitTests;
 
 namespace Terminal.Gui.ViewMouseTests;
 
@@ -95,7 +96,7 @@ public class MouseTests : TestsAllViews
 
         view.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -125,7 +126,7 @@ public class MouseTests : TestsAllViews
 
         view.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -155,7 +156,7 @@ public class MouseTests : TestsAllViews
 
         view.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -166,7 +167,6 @@ public class MouseTests : TestsAllViews
     [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released)]
     public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Button_Press_Release_Clicks (MouseFlags pressed, MouseFlags released)
     {
-        Application.Init (new FakeDriver ());
         var me = new MouseEventArgs ();
 
         var view = new View
@@ -177,28 +177,43 @@ public class MouseTests : TestsAllViews
             WantMousePositionReports = true
         };
 
+        // Setup components for mouse held down
+        var timed = new TimedEvents ();
+        var grab = new MouseGrabHandler ();
+        view.MouseHeldDown = new MouseHeldDown (view, timed, grab);
+
+        // Register callback for what to do when the mouse is held down
         var clickedCount = 0;
+        view.MouseHeldDown.MouseIsHeldDownTick += (_, _) => clickedCount++;
 
-        view.MouseClick += (s, e) => clickedCount++;
+        // Mouse is currently not held down so should be no timers running
+        Assert.Empty(timed.Timeouts);
 
+        // When mouse is held down
         me.Flags = pressed;
         view.NewMouseEvent (me);
         Assert.Equal (0, clickedCount);
         me.Handled = false;
 
-        me.Flags = pressed;
-        view.NewMouseEvent (me);
+        // A timer should begin
+        var t = Assert.Single (timed.Timeouts);
+
+        // Invoke the timer
+        t.Value.Callback.Invoke ();
+
+        // Event should have been raised
         Assert.Equal (1, clickedCount);
-        me.Handled = false;
+        Assert.NotEmpty(timed.Timeouts);
 
+        // When mouse is released
         me.Flags = released;
         view.NewMouseEvent (me);
+
+        // timer should stop
+        Assert.Empty (timed.Timeouts);
         Assert.Equal (1, clickedCount);
 
         view.Dispose ();
-
-        // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
-        Application.ResetState (true);
     }
 
     [Theory]
@@ -212,7 +227,6 @@ public class MouseTests : TestsAllViews
         MouseFlags clicked
     )
     {
-        Application.Init (new FakeDriver ());
         var me = new MouseEventArgs ();
 
         var view = new View
@@ -223,39 +237,49 @@ public class MouseTests : TestsAllViews
             WantMousePositionReports = true
         };
 
+        // Setup components for mouse held down
+        var timed = new TimedEvents ();
+        var grab = new MouseGrabHandler ();
+        view.MouseHeldDown = new MouseHeldDown (view, timed, grab);
+
+        // Register callback for what to do when the mouse is held down
         var clickedCount = 0;
+        view.MouseHeldDown.MouseIsHeldDownTick += (_, _) => clickedCount++;
 
-        view.MouseClick += (s, e) => clickedCount++;
+        Assert.Empty (timed.Timeouts);
 
         me.Flags = pressed;
         view.NewMouseEvent (me);
         Assert.Equal (0, clickedCount);
         me.Handled = false;
 
+        Assert.NotEmpty(timed.Timeouts);
+        Assert.Single (timed.Timeouts).Value.Callback.Invoke ();
+
         me.Flags = pressed;
         view.NewMouseEvent (me);
         Assert.Equal (1, clickedCount);
         me.Handled = false;
 
+        Assert.NotEmpty (timed.Timeouts);
+
         me.Flags = released;
         view.NewMouseEvent (me);
         Assert.Equal (1, clickedCount);
         me.Handled = false;
 
+        Assert.Empty (timed.Timeouts);
+
         me.Flags = clicked;
         view.NewMouseEvent (me);
         Assert.Equal (1, clickedCount);
 
         view.Dispose ();
-
-        // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
-        Application.ResetState (true);
     }
 
     [Fact]
     public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Move_InViewport_OutOfViewport_Keeps_Counting ()
     {
-        Application.Init (new FakeDriver ());
         var me = new MouseEventArgs ();
 
         var view = new View
@@ -266,9 +290,14 @@ public class MouseTests : TestsAllViews
             WantMousePositionReports = true
         };
 
-        var clickedCount = 0;
+        // Setup components for mouse held down
+        var timed = new TimedEvents ();
+        var grab = new MouseGrabHandler ();
+        view.MouseHeldDown = new MouseHeldDown (view, timed, grab);
 
-        view.MouseClick += (s, e) => clickedCount++;
+        // Register callback for what to do when the mouse is held down
+        var clickedCount = 0;
+        view.MouseHeldDown.MouseIsHeldDownTick += (_, _) => clickedCount++;
 
         // Start in Viewport
         me.Flags = MouseFlags.Button1Pressed;
@@ -277,17 +306,30 @@ public class MouseTests : TestsAllViews
         Assert.Equal (0, clickedCount);
         me.Handled = false;
 
+        // Mouse is held down so timer should be ticking
+        Assert.NotEmpty (timed.Timeouts);
+        Assert.Equal (clickedCount,0);
+
+        // Don't wait, just force it to expire
+        Assert.Single (timed.Timeouts).Value.Callback.Invoke ();
+        Assert.Equal (clickedCount, 1);
+
         // Move out of Viewport
         me.Flags = MouseFlags.Button1Pressed;
         me.Position = me.Position with { X = 1 };
         view.NewMouseEvent (me);
-        Assert.Equal (1, clickedCount);
+
+        Assert.Single (timed.Timeouts).Value.Callback.Invoke ();
+        Assert.Equal (clickedCount, 2);
+
         me.Handled = false;
 
         // Move into Viewport
         me.Flags = MouseFlags.Button1Pressed;
         me.Position = me.Position with { X = 0 };
         view.NewMouseEvent (me);
+
+        Assert.NotEmpty (timed.Timeouts);
         Assert.Equal (2, clickedCount);
         me.Handled = false;
 
@@ -295,13 +337,13 @@ public class MouseTests : TestsAllViews
         me.Flags = MouseFlags.Button1Pressed;
         me.Position = me.Position with { X = 0 };
         view.NewMouseEvent (me);
+
+        Assert.Single (timed.Timeouts).Value.Callback.Invoke ();
+
         Assert.Equal (3, clickedCount);
         me.Handled = false;
 
         view.Dispose ();
-
-        // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
-        Application.ResetState (true);
     }
 
     //[Theory]
@@ -335,7 +377,7 @@ public class MouseTests : TestsAllViews
 
     //    testView.Dispose ();
 
-    //    // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
+    //    // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
     //    Application.ResetState (true);
 
     //}
@@ -400,7 +442,7 @@ public class MouseTests : TestsAllViews
 
         testView.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -462,7 +504,7 @@ public class MouseTests : TestsAllViews
 
         testView.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -525,7 +567,7 @@ public class MouseTests : TestsAllViews
 
         testView.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -589,7 +631,7 @@ public class MouseTests : TestsAllViews
 
         testView.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
         Application.ResetState (true);
     }
     private class MouseEventTestView : View

+ 2 - 2
Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs

@@ -2571,11 +2571,11 @@ Edit
 
             if (i is < 0 or > 0)
             {
-                Assert.Equal (menu, Application.MouseGrabView);
+                Assert.Equal (menu, Application.MouseGrabHandler.MouseGrabView);
             }
             else
             {
-                Assert.Equal (menuBar, Application.MouseGrabView);
+                Assert.Equal (menuBar, Application.MouseGrabHandler.MouseGrabView);
             }
 
             Assert.Equal ("_Edit", miCurrent.Parent.Title);

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

@@ -106,6 +106,9 @@ public class SpinnerViewTests (ITestOutputHelper output)
         top.Add (view);
         Application.Begin (top);
 
+        // Required to clear the initial 'Invoke nothing' that Begin does
+        Application.MainLoop.TimedEvents.Timeouts.Clear ();
+
         Assert.Equal (1, view.Frame.Width);
         Assert.Equal (1, view.Frame.Height);
 

+ 28 - 28
Tests/UnitTests/Views/ToplevelTests.cs

@@ -305,17 +305,17 @@ public class ToplevelTests
                                      }
                                      else if (iterations == 2)
                                      {
-                                         Assert.Null (Application.MouseGrabView);
+                                         Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
                                          // Grab the mouse
                                          Application.RaiseMouseEvent (new () { ScreenPosition = new (3, 2), Flags = MouseFlags.Button1Pressed });
 
-                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabView);
+                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView);
                                          Assert.Equal (new (2, 2, 10, 3), Application.Top.Frame);
                                      }
                                      else if (iterations == 3)
                                      {
-                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabView);
+                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView);
 
                                          // Drag to left
                                          Application.RaiseMouseEvent (
@@ -326,19 +326,19 @@ public class ToplevelTests
                                                                       });
                                          Application.LayoutAndDraw ();
 
-                                         Assert.Equal (Application.Top.Border, Application.MouseGrabView);
+                                         Assert.Equal (Application.Top.Border, Application.MouseGrabHandler.MouseGrabView);
                                          Assert.Equal (new (1, 2, 10, 3), Application.Top.Frame);
                                      }
                                      else if (iterations == 4)
                                      {
-                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabView);
+                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView);
                                          Assert.Equal (new (1, 2), Application.Top.Frame.Location);
 
-                                         Assert.Equal (Application.Top.Border, Application.MouseGrabView);
+                                         Assert.Equal (Application.Top.Border, Application.MouseGrabHandler.MouseGrabView);
                                      }
                                      else if (iterations == 5)
                                      {
-                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabView);
+                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView);
 
                                          // Drag up
                                          Application.RaiseMouseEvent (
@@ -349,26 +349,26 @@ public class ToplevelTests
                                                                       });
                                          Application.LayoutAndDraw ();
 
-                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabView);
+                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView);
                                          Assert.Equal (new (1, 1, 10, 3), Application.Top.Frame);
                                      }
                                      else if (iterations == 6)
                                      {
-                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabView);
+                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView);
                                          Assert.Equal (new (1, 1), Application.Top.Frame.Location);
 
-                                         Assert.Equal (Application.Top.Border, Application.MouseGrabView);
+                                         Assert.Equal (Application.Top.Border, Application.MouseGrabHandler.MouseGrabView);
                                          Assert.Equal (new (1, 1, 10, 3), Application.Top.Frame);
                                      }
                                      else if (iterations == 7)
                                      {
-                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabView);
+                                         Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView);
 
                                          // Ungrab the mouse
                                          Application.RaiseMouseEvent (new () { ScreenPosition = new (2, 1), Flags = MouseFlags.Button1Released });
                                          Application.LayoutAndDraw ();
 
-                                         Assert.Null (Application.MouseGrabView);
+                                         Assert.Null (Application.MouseGrabHandler.MouseGrabView);
                                      }
                                      else if (iterations == 8)
                                      {
@@ -411,7 +411,7 @@ public class ToplevelTests
                                      {
                                          location = win.Frame;
 
-                                         Assert.Null (Application.MouseGrabView);
+                                         Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
                                          // Grab the mouse
                                          Application.RaiseMouseEvent (
@@ -420,11 +420,11 @@ public class ToplevelTests
                                                                           ScreenPosition = new (win.Frame.X, win.Frame.Y), Flags = MouseFlags.Button1Pressed
                                                                       });
 
-                                         Assert.Equal (win.Border, Application.MouseGrabView);
+                                         Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView);
                                      }
                                      else if (iterations == 2)
                                      {
-                                         Assert.Equal (win.Border, Application.MouseGrabView);
+                                         Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView);
 
                                          // Drag to left
                                          movex = 1;
@@ -438,18 +438,18 @@ public class ToplevelTests
                                                                               | MouseFlags.ReportMousePosition
                                                                       });
 
-                                         Assert.Equal (win.Border, Application.MouseGrabView);
+                                         Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView);
                                      }
                                      else if (iterations == 3)
                                      {
                                          // we should have moved +1, +0
-                                         Assert.Equal (win.Border, Application.MouseGrabView);
-                                         Assert.Equal (win.Border, Application.MouseGrabView);
+                                         Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView);
+                                         Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView);
                                          location.Offset (movex, movey);
                                      }
                                      else if (iterations == 4)
                                      {
-                                         Assert.Equal (win.Border, Application.MouseGrabView);
+                                         Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView);
 
                                          // Drag up
                                          movex = 0;
@@ -463,18 +463,18 @@ public class ToplevelTests
                                                                               | MouseFlags.ReportMousePosition
                                                                       });
 
-                                         Assert.Equal (win.Border, Application.MouseGrabView);
+                                         Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView);
                                      }
                                      else if (iterations == 5)
                                      {
                                          // we should have moved +0, -1
-                                         Assert.Equal (win.Border, Application.MouseGrabView);
+                                         Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView);
                                          location.Offset (movex, movey);
                                          Assert.Equal (location, win.Frame);
                                      }
                                      else if (iterations == 6)
                                      {
-                                         Assert.Equal (win.Border, Application.MouseGrabView);
+                                         Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView);
 
                                          // Ungrab the mouse
                                          movex = 0;
@@ -487,7 +487,7 @@ public class ToplevelTests
                                                                           Flags = MouseFlags.Button1Released
                                                                       });
 
-                                         Assert.Null (Application.MouseGrabView);
+                                         Assert.Null (Application.MouseGrabHandler.MouseGrabView);
                                      }
                                      else if (iterations == 7)
                                      {
@@ -602,11 +602,11 @@ public class ToplevelTests
         Assert.Equal (new (0, 0, 40, 10), top.Frame);
         Assert.Equal (new (0, 0, 20, 3), window.Frame);
 
-        Assert.Null (Application.MouseGrabView);
+        Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
         Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed });
 
-        Assert.Equal (window.Border, Application.MouseGrabView);
+        Assert.Equal (window.Border, Application.MouseGrabHandler.MouseGrabView);
 
         Application.RaiseMouseEvent (
                                      new ()
@@ -694,14 +694,14 @@ public class ToplevelTests
 
         RunState rs = Application.Begin (window);
 
-        Assert.Null (Application.MouseGrabView);
+        Assert.Null (Application.MouseGrabHandler.MouseGrabView);
         Assert.Equal (new (0, 0, 10, 3), window.Frame);
 
         Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed });
 
         var firstIteration = false;
         Application.RunIteration (ref rs, firstIteration);
-        Assert.Equal (window.Border, Application.MouseGrabView);
+        Assert.Equal (window.Border, Application.MouseGrabHandler.MouseGrabView);
 
         Assert.Equal (new (0, 0, 10, 3), window.Frame);
 
@@ -713,7 +713,7 @@ public class ToplevelTests
 
         firstIteration = false;
         Application.RunIteration (ref rs, firstIteration);
-        Assert.Equal (window.Border, Application.MouseGrabView);
+        Assert.Equal (window.Border, Application.MouseGrabHandler.MouseGrabView);
         Assert.Equal (new (1, 1, 10, 3), window.Frame);
 
         Application.End (rs);

+ 81 - 0
Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs

@@ -0,0 +1,81 @@
+namespace Terminal.Gui.ApplicationTests;
+
+public class LogarithmicTimeoutTests
+{
+    [Fact]
+    public void Span_Should_Return_BaseDelay_When_Stage_Is_Zero ()
+    {
+        var baseDelay = TimeSpan.FromMilliseconds (1000);
+        var timeout = new LogarithmicTimeout (baseDelay, () => true);
+
+        Assert.Equal (TimeSpan.Zero, timeout.Span);
+    }
+
+    [Fact]
+    public void Span_Should_Increase_Logarithmically ()
+    {
+        var baseDelay = TimeSpan.FromMilliseconds (1000);
+        var timeout = new LogarithmicTimeout (baseDelay, () => true);
+
+        var stage0 = timeout.Span;
+
+        timeout.AdvanceStage (); // stage = 1
+        var stage1 = timeout.Span;
+
+        timeout.AdvanceStage (); // stage = 2
+        var stage2 = timeout.Span;
+
+        timeout.AdvanceStage (); // stage = 3
+        var stage3 = timeout.Span;
+
+        Assert.True (stage1 > stage0, "Stage 1 should be greater than stage 0");
+        Assert.True (stage2 > stage1, "Stage 2 should be greater than stage 1");
+        Assert.True (stage3 > stage2, "Stage 3 should be greater than stage 2");
+    }
+
+    [Theory]
+    [MemberData (nameof (GetLogarithmicTestData))]
+    public void Span_Should_Match_Expected_Logarithmic_Value (
+        double baseDelayMs, int stage, double expectedMs)
+    {
+        var baseDelay = TimeSpan.FromMilliseconds (baseDelayMs);
+        var timeout = new LogarithmicTimeout (baseDelay, () => true);
+
+        for (int i = 0; i < stage; i++)
+        {
+            timeout.AdvanceStage ();
+        }
+
+        double actualMs = timeout.Span.TotalMilliseconds;
+        double tolerance = 0.001; // Allow minor rounding error
+
+        Assert.InRange (actualMs, expectedMs - tolerance, expectedMs + tolerance);
+    }
+
+    public static IEnumerable<object []> GetLogarithmicTestData ()
+    {
+        // baseDelayMs, stage, expectedSpanMs
+        double baseMs = 1000;
+
+        yield return new object [] { baseMs, 0, 0 };
+        yield return new object [] { baseMs, 1, baseMs * Math.Log (2) };
+        yield return new object [] { baseMs, 2, baseMs * Math.Log (3) };
+        yield return new object [] { baseMs, 5, baseMs * Math.Log (6) };
+        yield return new object [] { baseMs, 10, baseMs * Math.Log (11) };
+    }
+
+
+    [Fact]
+    public void Reset_Should_Set_Stage_Back_To_Zero ()
+    {
+        var baseDelay = TimeSpan.FromMilliseconds (1000);
+        var timeout = new LogarithmicTimeout (baseDelay, () => true);
+
+        timeout.AdvanceStage ();
+        timeout.AdvanceStage ();
+        Assert.NotEqual (baseDelay, timeout.Span);
+
+        timeout.Reset ();
+        Assert.Equal (TimeSpan.Zero, timeout.Span);
+    }
+}

+ 70 - 0
Tests/UnitTestsParallelizable/Application/SmoothAcceleratingTimeoutTests.cs

@@ -0,0 +1,70 @@
+namespace Terminal.Gui.ApplicationTests;
+
+
+public class SmoothAcceleratingTimeoutTests
+{
+    [Fact]
+    public void Span_Should_Return_InitialDelay_On_StageZero ()
+    {
+        var initialDelay = TimeSpan.FromMilliseconds (500);
+        var minDelay = TimeSpan.FromMilliseconds (50);
+        double decayFactor = 0.7;
+
+        var timeout = new SmoothAcceleratingTimeout (initialDelay, minDelay, decayFactor, () => true);
+
+        Assert.Equal (initialDelay, timeout.Span);
+    }
+
+    [Fact]
+    public void Span_Should_Decrease_As_Stage_Increases ()
+    {
+        var initialDelay = TimeSpan.FromMilliseconds (500);
+        var minDelay = TimeSpan.FromMilliseconds (50);
+        double decayFactor = 0.7;
+
+        var timeout = new SmoothAcceleratingTimeout (initialDelay, minDelay, decayFactor, () => true);
+
+        var previousSpan = timeout.Span;
+        for (int i = 0; i < 10; i++)
+        {
+            timeout.AdvanceStage ();
+            var currentSpan = timeout.Span;
+            Assert.True (currentSpan <= previousSpan, $"Stage {i + 1}: {currentSpan} should be <= {previousSpan}");
+            previousSpan = currentSpan;
+        }
+    }
+
+    [Fact]
+    public void Span_Should_Not_Go_Below_MinDelay ()
+    {
+        var initialDelay = TimeSpan.FromMilliseconds (500);
+        var minDelay = TimeSpan.FromMilliseconds (50);
+        double decayFactor = 0.5;
+
+        var timeout = new SmoothAcceleratingTimeout (initialDelay, minDelay, decayFactor, () => true);
+
+        for (int i = 0; i < 100; i++)
+        {
+            timeout.AdvanceStage ();
+        }
+
+        Assert.Equal (minDelay, timeout.Span);
+    }
+
+    [Fact]
+    public void Reset_Should_Set_Stage_Back_To_Zero ()
+    {
+        var initialDelay = TimeSpan.FromMilliseconds (500);
+        var minDelay = TimeSpan.FromMilliseconds (50);
+        double decayFactor = 0.7;
+
+        var timeout = new SmoothAcceleratingTimeout (initialDelay, minDelay, decayFactor, () => true);
+
+        timeout.AdvanceStage ();
+        timeout.AdvanceStage ();
+        Assert.NotEqual (initialDelay, timeout.Span);
+
+        timeout.Reset ();
+        Assert.Equal (initialDelay, timeout.Span);
+    }
+}

+ 1 - 2
Tests/UnitTestsParallelizable/TestSetup.cs

@@ -40,8 +40,7 @@ public class GlobalTestSetup : IDisposable
 
         // Public Properties
         Assert.Null (Application.Top);
-        Assert.Null (Application.MouseGrabView);
-        Assert.Null (Application.WantContinuousButtonPressedView);
+        Assert.Null (Application.MouseGrabHandler.MouseGrabView);
 
         // Don't check Application.ForceDriver
         // Assert.Empty (Application.ForceDriver);

+ 0 - 38
docfx/docs/multitasking.md

@@ -119,44 +119,6 @@ public class ClockView : View
 - **Keep timer callbacks fast** - they run on the main thread
 - **Use appropriate intervals** - too frequent updates can impact performance
 
-## Idle Processing
-
-Idle handlers run when the application has no events to process, useful for background maintenance:
-
-```csharp
-public class AutoSaveView : View
-{
-    private object idleToken;
-    private DateTime lastSave = DateTime.Now;
-    
-    public AutoSaveView()
-    {
-        idleToken = Application.MainLoop.AddIdle(CheckAutoSave);
-    }
-    
-    private bool CheckAutoSave()
-    {
-        if (DateTime.Now - lastSave > TimeSpan.FromMinutes(5))
-        {
-            if (HasUnsavedChanges())
-            {
-                SaveDocument();
-                lastSave = DateTime.Now;
-            }
-        }
-        return true; // Continue idle processing
-    }
-    
-    protected override void Dispose(bool disposing)
-    {
-        if (disposing && idleToken != null)
-        {
-            Application.MainLoop.RemoveIdle(idleToken);
-        }
-        base.Dispose(disposing);
-    }
-}
-```
 
 ## Common Patterns