Explorar o código

Fixes #4317 - Refactor `Application.Mouse` for decoupling and parallelism (#4318)

* Initial plan

* Refactor Application.Mouse - Create IMouse interface and Mouse implementation

Co-authored-by: tig <[email protected]>

* Add enhanced documentation for Application.Mouse property

Co-authored-by: tig <[email protected]>

* Add parallelizable unit tests for IMouse interface

Co-authored-by: tig <[email protected]>

* Refactor Application.Mouse for decoupling and parallelism

Co-authored-by: tig <[email protected]>

* Move HandleMouseGrab method to IMouseGrabHandler interface

Co-authored-by: tig <[email protected]>

* Add parallelizable tests for IMouse and IMouseGrabHandler interfaces

Co-authored-by: tig <[email protected]>

* Add MouseEventRoutingTests - 27 parallelizable tests for View mouse event handling

Co-authored-by: tig <[email protected]>

* Fix terminology: Replace parent/child with superView/subView in MouseEventRoutingTests

Co-authored-by: tig <[email protected]>

* Fix coding standards: Use explicit types and target-typed new() in test files

Co-authored-by: tig <[email protected]>

* Update coding standards documentation with explicit var and target-typed new() guidance

Co-authored-by: tig <[email protected]>

* Refactor Application classes and improve maintainability

Refactored `Sixel` property to be immutable, enhancing thread safety.
Cleaned up `ApplicationImpl` by removing redundant fields, restructuring
methods (`CreateDriver`, `CreateSubcomponents`), and improving exception
handling. Updated `Run<T>` and `Shutdown` methods for consistency.

Standardized logging/debugging messages and fixed formatting issues.
Reorganized `IApplication` interface, added detailed XML documentation,
and grouped related methods logically.

Performed general code cleanup, including fixing typos, improving
readability, and removing legacy/unnecessary code to reduce technical debt.

* Code cleanup

* Remove unreferenced LayoutAndDraw method from ApplicationImpl

* Code cleanup and TODOs

- Updated namespaces to reflect the new structure.
- Added `Driver`, `Force16Colors`, and `ForceDriver` properties.
- Introduced `Sixel` collection for sixel image management.
- Added lifecycle methods: `GetDriverTypes`, `Shutdown`, and events.
- Refactored `Init` to support legacy and modern drivers.
- Improved driver event handling and screen abstraction.
- Updated `Run` method to align with the application lifecycle.
- Simplified `IConsoleDriver` documentation.
- Removed redundant methods and improved code readability.

* Refactor LayoutAndDraw logic for better encapsulation

Refactored `Application.Run` to delegate `LayoutAndDraw` to
`ApplicationImpl.Instance.LayoutAndDraw`, improving separation
of concerns. Renamed `forceDraw` to `forceRedraw` for clarity
and moved `LayoutAndDraw` implementation to `ApplicationImpl`.

Added a new `LayoutAndDraw` method in `ApplicationImpl` to
handle layout and drawing, including managing `TopLevels`,
handling active popovers, and refreshing the screen. Updated
the `IApplication` interface to reflect the new method and
improved its documentation.

Implemented `RequestStop` in `ApplicationImpl` and fixed
formatting inconsistencies in `Run<T>`. Added TODOs for future
refactoring to encapsulate `Top` and `TopLevels` into an
`IViewHierarchy` and move certain properties to `IApplication`.

* Refactor ApplicationImpl to enhance mouse and keyboard support

Added a new `Mouse` property to the `ApplicationImpl` class,
replacing its previous declaration, to improve mouse
functionality. Updated `MouseGrabHandler` to initialize with
a default instance of `MouseGrabHandler`.

Added comments to ensure the preservation of existing keyboard
settings (`QuitKey`, `ArrangeKey`, `NextTabKey`) for backward
compatibility. These changes enhance clarity, functionality,
and maintainability of the class.

* Merge IMouseGrabHandler into IMouse - consolidate mouse handling into single interface

Co-authored-by: tig <[email protected]>

* Rename Mouse to MouseImpl and Keyboard to KeyboardImpl for consistency

Co-authored-by: tig <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: tig <[email protected]>
Co-authored-by: Tig <[email protected]>
Copilot hai 1 mes
pai
achega
4974343e74
Modificáronse 43 ficheiros con 2261 adicións e 764 borrados
  1. 36 2
      .github/copilot-instructions.md
  2. 43 0
      CONTRIBUTING.md
  3. 5 1
      Terminal.Gui/App/Application.Driver.cs
  4. 84 75
      Terminal.Gui/App/Application.Initialization.cs
  5. 1 1
      Terminal.Gui/App/Application.Keyboard.cs
  6. 50 252
      Terminal.Gui/App/Application.Mouse.cs
  7. 7 32
      Terminal.Gui/App/Application.Run.cs
  8. 1 1
      Terminal.Gui/App/Application.Screen.cs
  9. 3 4
      Terminal.Gui/App/Application.cs
  10. 186 148
      Terminal.Gui/App/ApplicationImpl.cs
  11. 69 58
      Terminal.Gui/App/IApplication.cs
  12. 2 2
      Terminal.Gui/App/Keyboard/KeyboardImpl.cs
  13. 1 1
      Terminal.Gui/App/MainLoop/ApplicationMainLoop.cs
  14. 79 0
      Terminal.Gui/App/Mouse/IMouse.cs
  15. 8 0
      Terminal.Gui/App/Mouse/IMouseGrabHandler.cs
  16. 41 0
      Terminal.Gui/App/Mouse/MouseGrabHandler.cs
  17. 393 0
      Terminal.Gui/App/Mouse/MouseImpl.cs
  18. 1 3
      Terminal.Gui/Drivers/IConsoleDriver.cs
  19. 9 9
      Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs
  20. 2 2
      Terminal.Gui/ViewBase/Adornment/Border.cs
  21. 6 6
      Terminal.Gui/ViewBase/View.Mouse.cs
  22. 2 2
      Terminal.Gui/ViewBase/View.cs
  23. 1 1
      Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs
  24. 2 2
      Terminal.Gui/Views/ComboBox.cs
  25. 4 4
      Terminal.Gui/Views/Menuv1/Menu.cs
  26. 33 33
      Terminal.Gui/Views/Menuv1/MenuBar.cs
  27. 4 4
      Terminal.Gui/Views/ScrollBar/ScrollSlider.cs
  28. 2 2
      Terminal.Gui/Views/Slider/Slider.cs
  29. 6 6
      Terminal.Gui/Views/TextInput/TextField.cs
  30. 6 6
      Terminal.Gui/Views/TextInput/TextView.cs
  31. 3 3
      Tests/UnitTests/Application/ApplicationTests.cs
  32. 31 31
      Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs
  33. 1 1
      Tests/UnitTests/View/Adornment/ShadowStyleTests.cs
  34. 8 8
      Tests/UnitTests/View/Mouse/MouseTests.cs
  35. 2 2
      Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs
  36. 28 28
      Tests/UnitTests/Views/ToplevelTests.cs
  37. 33 33
      Tests/UnitTestsParallelizable/Application/KeyboardTests.cs
  38. 444 0
      Tests/UnitTestsParallelizable/Application/MouseInterfaceTests.cs
  39. 125 0
      Tests/UnitTestsParallelizable/Application/MouseTests.cs
  40. 1 1
      Tests/UnitTestsParallelizable/TestSetup.cs
  41. 498 0
      Tests/UnitTestsParallelizable/View/Mouse/MouseEventRoutingTests.cs
  42. BIN=BIN
      local_packages/Terminal.Gui.2.0.0.nupkg
  43. BIN=BIN
      local_packages/Terminal.Gui.2.0.0.snupkg

+ 36 - 2
.github/copilot-instructions.md

@@ -150,12 +150,44 @@ dotnet run --project Examples/UICatalog/UICatalog.csproj
 5. **Documentation is the Spec** - API docs are source of truth
 
 ### Coding Conventions
-- Use explicit types (avoid `var` except for basic types like `int`, `string`)
-- Use target-typed `new()`
+
+**⚠️ CRITICAL - These rules MUST be followed in ALL new code:**
+
+#### Type Declarations and Object Creation
+- **ALWAYS use explicit types** - Never use `var` except for basic types (`int`, `string`, `bool`, `double`, `float`, `decimal`, `char`, `byte`)
+  ```csharp
+  // ✅ CORRECT - Explicit types
+  View view = new () { Width = 10 };
+  MouseEventArgs args = new () { Position = new Point(5, 5) };
+  List<View?> views = new ();
+  var count = 0;  // OK - int is a basic type
+  var name = "test";  // OK - string is a basic type
+  
+  // ❌ WRONG - Using var for non-basic types
+  var view = new View { Width = 10 };
+  var args = new MouseEventArgs { Position = new Point(5, 5) };
+  var views = new List<View?>();
+  ```
+
+- **ALWAYS use target-typed `new()`** - Use `new ()` instead of `new TypeName()` when the type is already declared
+  ```csharp
+  // ✅ CORRECT - Target-typed new
+  View view = new () { Width = 10 };
+  MouseEventArgs args = new ();
+  
+  // ❌ WRONG - Redundant type name
+  View view = new View() { Width = 10 };
+  MouseEventArgs args = new MouseEventArgs();
+  ```
+
+#### Other Conventions
 - Follow `.editorconfig` settings (e.g., braces on new lines, spaces after keywords)
 - 4-space indentation
+- No trailing whitespace
 - See `CONTRIBUTING.md` for full guidelines
 
+**These conventions apply to ALL code - production code, test code, examples, and samples.**
+
 ## Testing Requirements
 
 ### Code Coverage
@@ -278,6 +310,8 @@ dotnet build --configuration Release --no-restore
 - ❌ Don't add tests to `UnitTests` if they can be parallelizable
 - ❌ Don't use `Application.Init` in new tests
 - ❌ Don't decrease code coverage
+- ❌ **Don't use `var` for non-basic types** (use explicit types)
+- ❌ **Don't use redundant type names with `new`** (use target-typed `new()`)
 - ❌ Don't add `var` everywhere (use explicit types)
 
 ## Additional Resources

+ 43 - 0
CONTRIBUTING.md

@@ -99,6 +99,49 @@ Follow the template instructions found on Github.
 * **Documentation is the Spec** - We care deeply about providing delightful developer documentation and are sticklers for grammar and clarity. If the code and the docs conflict, we are biased to believe what we wrote in the API documentation. This drives a virtuous cycle of clear thinking.
 
 **Terminal.Gui** uses a derivative of the [Microsoft C# Coding Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions), with any deviations from those (somewhat older) conventions codified in the .editorconfig for the solution, as well as even more specific definitions in team-shared dotsettings files, used by ReSharper and Rider.\
+
+### Critical Coding Standards
+
+**⚠️ These rules MUST be followed in ALL new code (production, tests, examples, samples):**
+
+#### Type Declarations and Object Creation
+
+1. **ALWAYS use explicit types** - Never use `var` except for basic types (`int`, `string`, `bool`, `double`, `float`, `decimal`, `char`, `byte`)
+
+   ```csharp
+   // ✅ CORRECT - Explicit types
+   View view = new () { Width = 10 };
+   MouseEventArgs args = new () { Position = new Point(5, 5) };
+   List<View?> views = new ();
+   var count = 0;  // OK - int is a basic type
+   var name = "test";  // OK - string is a basic type
+   
+   // ❌ WRONG - Using var for non-basic types
+   var view = new View { Width = 10 };
+   var args = new MouseEventArgs { Position = new Point(5, 5) };
+   var views = new List<View?>();
+   ```
+
+2. **ALWAYS use target-typed `new()`** - Use `new ()` instead of `new TypeName()` when the type is already declared
+
+   ```csharp
+   // ✅ CORRECT - Target-typed new
+   View view = new () { Width = 10 };
+   MouseEventArgs args = new ();
+   
+   // ❌ WRONG - Redundant type name
+   View view = new View() { Width = 10 };
+   MouseEventArgs args = new MouseEventArgs();
+   ```
+
+**Why these rules matter:**
+- Explicit types improve code readability and make the type system more apparent
+- Target-typed `new()` reduces redundancy while maintaining clarity
+- Consistency across the codebase makes it easier for all contributors to read and maintain code
+- These conventions align with modern C# best practices (C# 9.0+)
+
+### Code Formatting
+
 Before you commit code, please run the formatting rules on **only the code file(s) you have modified**, in one of the following ways, in order of most preferred to least preferred:
 
  1. `Ctrl-E-C` if using ReSharper or Rider

+ 5 - 1
Terminal.Gui/App/Application.Driver.cs

@@ -6,9 +6,11 @@ public static partial class Application // Driver abstractions
 {
     internal static bool _forceFakeConsole;
 
+    // TODO: Add to IApplication
     /// <summary>Gets the <see cref="IConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.</summary>
     public static IConsoleDriver? Driver { get; internal set; }
 
+    // TODO: Add to IApplication
     // BUGBUG: Force16Colors should be nullable.
     /// <summary>
     ///     Gets or sets whether <see cref="Application.Driver"/> will be forced to output only the 16 colors defined in
@@ -18,6 +20,7 @@ public static partial class Application // Driver abstractions
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static bool Force16Colors { get; set; }
 
+    // TODO: Add to IApplication
     // BUGBUG: ForceDriver should be nullable.
     /// <summary>
     ///     Forces the use of the specified driver (one of "fake", "dotnet", "windows", or "unix"). If not
@@ -30,9 +33,10 @@ public static partial class Application // Driver abstractions
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static string ForceDriver { get; set; } = string.Empty;
 
+    // TODO: Add to IApplication
     /// <summary>
     /// Collection of sixel images to write out to screen when updating.
     /// Only add to this collection if you are sure terminal supports sixel format.
     /// </summary>
-    public static List<SixelToRender> Sixel = new List<SixelToRender> ();
+    public static List<SixelToRender> Sixel { get; } = new List<SixelToRender> ();
 }

+ 84 - 75
Terminal.Gui/App/Application.Initialization.cs

@@ -5,10 +5,42 @@ using System.Reflection;
 
 namespace Terminal.Gui.App;
 
-public static partial class Application // Initialization (Init/Shutdown)
+public static partial class Application // Lifecycle (Init/Shutdown)
 {
+    // TODO: Add to IApplication
+    /// <summary>Gets of list of <see cref="IConsoleDriver"/> types and type names that are available.</summary>
+    /// <returns></returns>
+    [RequiresUnreferencedCode ("AOT")]
+    public static (List<Type?>, List<string?>) GetDriverTypes ()
+    {
+        // use reflection to get the list of drivers
+        List<Type?> driverTypes = new ();
+
+        // Only inspect the IConsoleDriver assembly
+        Assembly asm = typeof (IConsoleDriver).Assembly;
+
+        foreach (Type? type in asm.GetTypes ())
+        {
+            if (typeof (IConsoleDriver).IsAssignableFrom (type) && type is { IsAbstract: false, IsClass: true })
+            {
+                driverTypes.Add (type);
+            }
+        }
+
+        List<string?> driverTypeNames = driverTypes
+                                        .Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
+                                        .Select (d => d!.Name)
+                                        .Union (["dotnet", "windows", "unix", "fake"])
+                                        .ToList ()!;
 
-    /// <summary>Initializes a new instance of a Terminal.Gui Application. <see cref="Shutdown"/> must be called when the application is closing.</summary>
+        return (driverTypes, driverTypeNames);
+    }
+
+    // TODO: Add to IApplicationLifecycle
+    /// <summary>
+    ///     Initializes a new instance of a Terminal.Gui Application. <see cref="Shutdown"/> must be called when the
+    ///     application is closing.
+    /// </summary>
     /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
     /// <para>
     ///     This function loads the right <see cref="IConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
@@ -44,27 +76,60 @@ public static partial class Application // Initialization (Init/Shutdown)
         // that isn't supported by the modern application architecture
         if (driver is null)
         {
-            var driverNameToCheck = string.IsNullOrWhiteSpace (driverName) ? ForceDriver : driverName;
+            string driverNameToCheck = string.IsNullOrWhiteSpace (driverName) ? ForceDriver : driverName;
+
             if (!string.IsNullOrEmpty (driverNameToCheck))
             {
                 (List<Type?> drivers, List<string?> driverTypeNames) = GetDriverTypes ();
                 Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (driverNameToCheck, StringComparison.InvariantCultureIgnoreCase));
-                
+
                 // If it's a legacy IConsoleDriver (not a Facade), use InternalInit which supports legacy drivers
                 if (driverType is { } && !typeof (IConsoleDriverFacade).IsAssignableFrom (driverType))
                 {
                     InternalInit (driver, driverName);
+
                     return;
                 }
             }
         }
-        
+
         // Otherwise delegate to the ApplicationImpl instance (which uses the modern architecture)
         ApplicationImpl.Instance.Init (driver, driverName ?? ForceDriver);
     }
 
-    internal static int MainThreadId { get; set; } = -1;
+    // TODO: Add to IApplicationLifecycle
+    /// <summary>
+    ///     Gets whether the application has been initialized with <see cref="Init"/> and not yet shutdown with
+    ///     <see cref="Shutdown"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         The <see cref="InitializedChanged"/> event is raised after the <see cref="Init"/> and <see cref="Shutdown"/>
+    ///         methods have been called.
+    ///     </para>
+    /// </remarks>
+    public static bool Initialized { get; internal set; }
+
+    // TODO: Add to IApplicationLifecycle
+    /// <summary>
+    ///     This event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
+    /// </summary>
+    /// <remarks>
+    ///     Intended to support unit tests that need to know when the application has been initialized.
+    /// </remarks>
+    public static event EventHandler<EventArgs<bool>>? InitializedChanged;
 
+    // TODO: Add to IApplicationLifecycle
+    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    /// <remarks>
+    ///     Shutdown must be called for every call to <see cref="Init"/> or
+    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
+    ///     up (Disposed)
+    ///     and terminal settings are restored.
+    /// </remarks>
+    public static void Shutdown () { ApplicationImpl.Instance.Shutdown (); }
+
+    // TODO: Add to IApplicationLifecycle
     // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop.
     //
     // Called from:
@@ -95,7 +160,7 @@ public static partial class Application // Initialization (Init/Shutdown)
         if (!calledViaRunT)
         {
             // Reset all class variables (Application is a singleton).
-            ResetState (ignoreDisposed: true);
+            ResetState (true);
         }
 
         // For UnitTests
@@ -120,7 +185,7 @@ public static partial class Application // Initialization (Init/Shutdown)
             //{
             //    (List<Type?> drivers, List<string?> driverTypeNames) = GetDriverTypes ();
             //    Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase));
-                
+
             //    if (driverType is { } && !typeof (IConsoleDriverFacade).IsAssignableFrom (driverType))
             //    {
             //        // This is a legacy driver (not a ConsoleDriverFacade)
@@ -128,12 +193,13 @@ public static partial class Application // Initialization (Init/Shutdown)
             //        useLegacyDriver = true;
             //    }
             //}
-            
+
             //// Use the modern application architecture
             //if (!useLegacyDriver)
             {
                 ApplicationImpl.Instance.Init (driver, driverName);
                 Debug.Assert (Driver is { });
+
                 return;
             }
         }
@@ -174,6 +240,14 @@ public static partial class Application // Initialization (Init/Shutdown)
         InitializedChanged?.Invoke (null, new (init));
     }
 
+    internal static int MainThreadId { get; set; } = -1;
+
+    // TODO: Add to IApplicationLifecycle
+    /// <summary>
+    ///     Raises the <see cref="InitializedChanged"/> event.
+    /// </summary>
+    internal static void OnInitializedChanged (object sender, EventArgs<bool> e) { InitializedChanged?.Invoke (sender, e); }
+
     internal static void SubscribeDriverEvents ()
     {
         ArgumentNullException.ThrowIfNull (Driver);
@@ -194,74 +268,9 @@ public static partial class Application // Initialization (Init/Shutdown)
         Driver.MouseEvent -= Driver_MouseEvent;
     }
 
-    private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
     private static void Driver_KeyDown (object? sender, Key e) { RaiseKeyDownEvent (e); }
     private static void Driver_KeyUp (object? sender, Key e) { RaiseKeyUpEvent (e); }
     private static void Driver_MouseEvent (object? sender, MouseEventArgs e) { RaiseMouseEvent (e); }
 
-    /// <summary>Gets of list of <see cref="IConsoleDriver"/> types and type names that are available.</summary>
-    /// <returns></returns>
-    [RequiresUnreferencedCode ("AOT")]
-    public static (List<Type?>, List<string?>) GetDriverTypes ()
-    {
-        // use reflection to get the list of drivers
-        List<Type?> driverTypes = new ();
-
-        // Only inspect the IConsoleDriver assembly
-        var asm = typeof (IConsoleDriver).Assembly;
-
-        foreach (Type? type in asm.GetTypes ())
-        {
-            if (typeof (IConsoleDriver).IsAssignableFrom (type) &&
-                type is { IsAbstract: false, IsClass: true })
-            {
-                driverTypes.Add (type);
-            }
-        }
-
-        List<string?> driverTypeNames = driverTypes
-                                        .Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
-                                        .Select (d => d!.Name)
-                                        .Union (["dotnet", "windows", "unix", "fake"])
-                                        .ToList ()!;
-
-
-
-        return (driverTypes, driverTypeNames);
-    }
-
-    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
-    /// <remarks>
-    ///     Shutdown must be called for every call to <see cref="Init"/> or
-    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
-    ///     up (Disposed)
-    ///     and terminal settings are restored.
-    /// </remarks>
-    public static void Shutdown () => ApplicationImpl.Instance.Shutdown ();
-
-    /// <summary>
-    ///     Gets whether the application has been initialized with <see cref="Init"/> and not yet shutdown with <see cref="Shutdown"/>.
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    ///     The <see cref="InitializedChanged"/> event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
-    /// </para>
-    /// </remarks>
-    public static bool Initialized { get; internal set; }
-
-    /// <summary>
-    ///     This event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
-    /// </summary>
-    /// <remarks>
-    ///     Intended to support unit tests that need to know when the application has been initialized.
-    /// </remarks>
-    public static event EventHandler<EventArgs<bool>>? InitializedChanged;
-
-    /// <summary>
-    ///  Raises the <see cref="InitializedChanged"/> event.
-    /// </summary>
-    internal static void OnInitializedChanged (object sender, EventArgs<bool> e)
-    {
-        Application.InitializedChanged?.Invoke (sender, e);
-    }
+    private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
 }

+ 1 - 1
Terminal.Gui/App/Application.Keyboard.cs

@@ -83,7 +83,7 @@ public static partial class Application // Keyboard handling
 
     internal static void AddKeyBindings ()
     {
-        if (Keyboard is Keyboard keyboard)
+        if (Keyboard is KeyboardImpl keyboard)
         {
             keyboard.AddKeyBindings ();
         }

+ 50 - 252
Terminal.Gui/App/Application.Mouse.cs

@@ -5,176 +5,39 @@ namespace Terminal.Gui.App;
 
 public static partial class Application // Mouse handling
 {
-    /// <summary>
-    /// INTERNAL API: Holds the last mouse position.
-    /// </summary>
-    internal static Point? LastMousePosition { get; set; }
-
     /// <summary>
     ///     Gets the most recent position of the mouse.
     /// </summary>
-    public static Point? GetLastMousePosition () { return LastMousePosition; }
+    public static Point? GetLastMousePosition () { return Mouse.GetLastMousePosition (); }
 
     /// <summary>Disable or enable the mouse. The mouse is enabled by default.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
-    public static bool IsMouseDisabled { get; set; }
-
-    /// <summary>
-    /// Static reference to the current <see cref="IApplication"/> <see cref="IMouseGrabHandler"/>.
-    /// </summary>
-    public static IMouseGrabHandler MouseGrabHandler
+    public static bool IsMouseDisabled
     {
-        get => ApplicationImpl.Instance.MouseGrabHandler;
-        set => ApplicationImpl.Instance.MouseGrabHandler = value ??
-                                                           throw new ArgumentNullException(nameof(value));
+        get => Mouse.IsMouseDisabled;
+        set => Mouse.IsMouseDisabled = value;
     }
 
     /// <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.
+    ///     Gets the <see cref="IMouse"/> instance that manages mouse event handling and state.
     /// </summary>
-    /// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
-    /// <param name="mouseEvent">The mouse event with coordinates relative to the screen.</param>
-    internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
-    {
-        if (Initialized)
-        {
-            // LastMousePosition is a static; only set if the application is initialized.
-            LastMousePosition = mouseEvent.ScreenPosition;
-        }
-
-        if (IsMouseDisabled)
-        {
-            return;
-        }
-
-        // The position of the mouse is the same as the screen position at the application level.
-        //Debug.Assert (mouseEvent.Position == mouseEvent.ScreenPosition);
-        mouseEvent.Position = mouseEvent.ScreenPosition;
-
-        List<View?> currentViewsUnderMouse = View.GetViewsUnderLocation (mouseEvent.ScreenPosition, ViewportSettingsFlags.TransparentMouse);
-
-        View? deepestViewUnderMouse = currentViewsUnderMouse.LastOrDefault ();
-
-        if (deepestViewUnderMouse is { })
-        {
-#if DEBUG_IDISPOSABLE
-            if (View.EnableDebugIDisposableAsserts && deepestViewUnderMouse.WasDisposed)
-            {
-                throw new ObjectDisposedException (deepestViewUnderMouse.GetType ().FullName);
-            }
-#endif
-            mouseEvent.View = deepestViewUnderMouse;
-        }
-
-        MouseEvent?.Invoke (null, mouseEvent);
-
-        if (mouseEvent.Handled)
-        {
-            return;
-        }
-
-        // Dismiss the Popover if the user presses mouse outside of it
-        if (mouseEvent.IsPressed
-            && Popover?.GetActivePopover () as View is { Visible: true } visiblePopover
-            && View.IsInHierarchy (visiblePopover, deepestViewUnderMouse, includeAdornments: true) is false)
-        {
-            ApplicationPopover.HideWithQuitCommand (visiblePopover);
-
-            // Recurse once so the event can be handled below the popover
-            RaiseMouseEvent (mouseEvent);
-
-            return;
-        }
-
-        if (HandleMouseGrab (deepestViewUnderMouse, mouseEvent))
-        {
-            return;
-        }
-
-        // 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)
-        {
-            return;
-        }
-
-        // if the mouse is outside the Application.Top or Application.Popover hierarchy, we don't want to
-        // send the mouse event to the deepest view under the mouse.
-        if (!View.IsInHierarchy (Application.Top, deepestViewUnderMouse, true) && !View.IsInHierarchy (Popover?.GetActivePopover () as View, deepestViewUnderMouse, true))
-        {
-            return;
-        }
-
-        // Create a view-relative mouse event to send to the view that is under the mouse.
-        MouseEventArgs viewMouseEvent;
-
-        if (deepestViewUnderMouse is Adornment adornment)
-        {
-            Point frameLoc = adornment.ScreenToFrame (mouseEvent.ScreenPosition);
-
-            viewMouseEvent = new ()
-            {
-                Position = frameLoc,
-                Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.ScreenPosition,
-                View = deepestViewUnderMouse
-            };
-        }
-        else if (deepestViewUnderMouse.ViewportToScreen (Rectangle.Empty with { Size = deepestViewUnderMouse.Viewport.Size }).Contains (mouseEvent.ScreenPosition))
-        {
-            Point viewportLocation = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);
-
-            viewMouseEvent = new ()
-            {
-                Position = viewportLocation,
-                Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.ScreenPosition,
-                View = deepestViewUnderMouse
-            };
-        }
-        else
-        {
-            // The mouse was outside any View's Viewport.
-            // Debug.Fail ("This should never happen. If it does please file an Issue!!");
-
-            return;
-        }
-
-        RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse);
-
-        while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabHandler.MouseGrabView is not { })
-        {
-            if (deepestViewUnderMouse is Adornment adornmentView)
-            {
-                deepestViewUnderMouse = adornmentView.Parent?.SuperView;
-            }
-            else
-            {
-                deepestViewUnderMouse = deepestViewUnderMouse.SuperView;
-            }
-
-            if (deepestViewUnderMouse is null)
-            {
-                break;
-            }
-
-            Point boundsPoint = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);
-
-            viewMouseEvent = new ()
-            {
-                Position = boundsPoint,
-                Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.ScreenPosition,
-                View = deepestViewUnderMouse
-            };
-        }
-    }
-
+    /// <remarks>
+    ///     <para>
+    ///         This property provides access to mouse-related functionality in a way that supports
+    ///         parallel test execution by avoiding static state.
+    ///     </para>
+    ///     <para>
+    ///         New code should use <c>Application.Mouse</c> instead of the static properties and methods
+    ///         for better testability. Legacy static properties like <see cref="IsMouseDisabled"/> and
+    ///         <see cref="GetLastMousePosition"/> are retained for backward compatibility.
+    ///     </para>
+    /// </remarks>
+    public static IMouse Mouse => ApplicationImpl.Instance.Mouse;
 
 #pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
     /// <summary>
-    /// Raised when a mouse event occurs. Can be cancelled by setting <see cref="HandledEventArgs.Handled"/> to <see langword="true"/>.
+    ///     Raised when a mouse event occurs. Can be cancelled by setting <see cref="HandledEventArgs.Handled"/> to
+    ///     <see langword="true"/>.
     /// </summary>
     /// <remarks>
     ///     <para>
@@ -184,59 +47,34 @@ public static partial class Application // Mouse handling
     ///         <see cref="MouseEventArgs.View"/> will be the deepest view under the mouse.
     ///     </para>
     ///     <para>
-    ///         <see cref="MouseEventArgs.Position"/> coordinates are view-relative. Only valid if <see cref="MouseEventArgs.View"/> is set.
+    ///         <see cref="MouseEventArgs.Position"/> coordinates are view-relative. Only valid if
+    ///         <see cref="MouseEventArgs.View"/> is set.
     ///     </para>
     ///     <para>
     ///         Use this even to handle mouse events at the application level, before View-specific handling.
     ///     </para>
     /// </remarks>
-    public static event EventHandler<MouseEventArgs>? MouseEvent;
-#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
-
-    internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent)
+    public static event EventHandler<MouseEventArgs>? MouseEvent
     {
-        if (MouseGrabHandler.MouseGrabView is { })
-        {
-#if DEBUG_IDISPOSABLE
-            if (View.EnableDebugIDisposableAsserts && MouseGrabHandler.MouseGrabView.WasDisposed)
-            {
-                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 = MouseGrabHandler.MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);
-
-            var viewRelativeMouseEvent = new MouseEventArgs
-            {
-                Position = frameLoc,
-                Flags = mouseEvent.Flags,
-                ScreenPosition = mouseEvent.ScreenPosition,
-                View = deepestViewUnderMouse ?? MouseGrabHandler.MouseGrabView
-            };
-
-            //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
-            if (MouseGrabHandler.MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
-            {
-                return true;
-            }
-
-            // ReSharper disable once ConditionIsAlwaysTrueOrFalse
-            if (MouseGrabHandler.MouseGrabView is null && deepestViewUnderMouse is Adornment)
-            {
-                // The view that grabbed the mouse has been disposed
-                return true;
-            }
-        }
-
-        return false;
+        add => Mouse.MouseEvent += value;
+        remove => Mouse.MouseEvent -= value;
     }
+#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
 
     /// <summary>
-    ///     INTERNAL: Holds the non-<see cref="ViewportSettingsFlags.TransparentMouse"/> views that are currently under the mouse.
+    ///     INTERNAL: Holds the non-<see cref="ViewportSettingsFlags.TransparentMouse"/> views that are currently under the
+    ///     mouse.
     /// </summary>
-    internal static List<View?> CachedViewsUnderMouse { get; } = [];
+    internal static List<View?> CachedViewsUnderMouse => Mouse.CachedViewsUnderMouse;
+
+    /// <summary>
+    ///     INTERNAL API: Holds the last mouse position.
+    /// </summary>
+    internal static Point? LastMousePosition
+    {
+        get => Mouse.LastMousePosition;
+        set => Mouse.LastMousePosition = value;
+    }
 
     /// <summary>
     ///     INTERNAL: Raises the MouseEnter and MouseLeave events for the views that are under the mouse.
@@ -245,59 +83,19 @@ public static partial class Application // Mouse handling
     /// <param name="currentViewsUnderMouse">The most recent result from GetViewsUnderLocation().</param>
     internal static void RaiseMouseEnterLeaveEvents (Point screenPosition, List<View?> currentViewsUnderMouse)
     {
-        // Tell any views that are no longer under the mouse that the mouse has left
-        List<View?> viewsToLeave = CachedViewsUnderMouse.Where (v => v is { } && !currentViewsUnderMouse.Contains (v)).ToList ();
-
-        foreach (View? view in viewsToLeave)
-        {
-            if (view is null)
-            {
-                continue;
-            }
-
-            view.NewMouseLeaveEvent ();
-            CachedViewsUnderMouse.Remove (view);
-        }
-
-        // Tell any views that are now under the mouse that the mouse has entered and add them to the list
-        foreach (View? view in currentViewsUnderMouse)
-        {
-            if (view is null)
-            {
-                continue;
-            }
-
-            if (CachedViewsUnderMouse.Contains (view))
-            {
-                continue;
-            }
-
-            CachedViewsUnderMouse.Add (view);
-            var raise = false;
-
-            if (view is Adornment { Parent: { } } adornmentView)
-            {
-                Point superViewLoc = adornmentView.Parent.SuperView?.ScreenToViewport (screenPosition) ?? screenPosition;
-                raise = adornmentView.Contains (superViewLoc);
-            }
-            else
-            {
-                Point superViewLoc = view.SuperView?.ScreenToViewport (screenPosition) ?? screenPosition;
-                raise = view.Contains (superViewLoc);
-            }
-
-            if (!raise)
-            {
-                continue;
-            }
+        Mouse.RaiseMouseEnterLeaveEvents (screenPosition, currentViewsUnderMouse);
+    }
 
-            CancelEventArgs eventArgs = new ();
-            bool? cancelled = view.NewMouseEnterEvent (eventArgs);
+    /// <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.
+    /// </summary>
+    /// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
+    /// <param name="mouseEvent">The mouse event with coordinates relative to the screen.</param>
+    internal static void RaiseMouseEvent (MouseEventArgs mouseEvent) { Mouse.RaiseMouseEvent (mouseEvent); }
 
-            if (cancelled is true || eventArgs.Cancel)
-            {
-                break;
-            }
-        }
-    }
+    /// <summary>
+    ///     INTERNAL: Clears mouse state during application reset.
+    /// </summary>
+    internal static void ResetMouseState () { Mouse.ResetState (); }
 }

+ 7 - 32
Terminal.Gui/App/Application.Run.cs

@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
 
 namespace Terminal.Gui.App;
 
-public static partial class Application // Run (Begin, Run, End, Stop)
+public static partial class Application // Run (Begin -> Run -> Layout/Draw -> End -> Stop)
 {
     /// <summary>Gets or sets the key to quit the application.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
@@ -71,9 +71,9 @@ public static partial class Application // Run (Begin, Run, End, Stop)
         //#endif
 
         // Ensure the mouse is ungrabbed.
-        if (MouseGrabHandler.MouseGrabView is { })
+        if (Mouse.MouseGrabView is { })
         {
-            MouseGrabHandler.UngrabMouse ();
+            Mouse.UngrabMouse ();
         }
 
         var rs = new RunState (toplevel);
@@ -187,7 +187,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
         toplevel.OnLoaded ();
 
-        LayoutAndDraw (true);
+        ApplicationImpl.Instance.LayoutAndDraw (true);
 
         if (PositionCursor ())
         {
@@ -406,40 +406,15 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     ///     If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and
     ///     should only be overriden for testing.
     /// </param>
-    public static void LayoutAndDraw (bool forceDraw = false)
+    public static void LayoutAndDraw (bool forceRedraw = false)
     {
-        List<View> tops = [.. TopLevels];
-
-        if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
-        {
-            visiblePopover.SetNeedsDraw ();
-            visiblePopover.SetNeedsLayout ();
-            tops.Insert (0, visiblePopover);
-        }
-
-        bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Screen.Size);
-
-        if (ClearScreenNextIteration)
-        {
-            forceDraw = true;
-            ClearScreenNextIteration = false;
-        }
-
-        if (forceDraw)
-        {
-            Driver?.ClearContents ();
-        }
-
-        View.SetClipToScreen ();
-        View.Draw (tops, neededLayout || forceDraw);
-        View.SetClipToScreen ();
-        Driver?.Refresh ();
+        ApplicationImpl.Instance.LayoutAndDraw (forceRedraw);
     }
 
     /// <summary>This event is raised on each iteration of the main loop.</summary>
     /// <remarks>See also <see cref="Timeout"/></remarks>
     public static event EventHandler<IterationEventArgs>? Iteration;
-    
+
     /// <summary>The <see cref="MainLoop"/> driver for the application</summary>
     /// <value>The main loop.</value>
     internal static MainLoop? MainLoop { get; set; }

+ 1 - 1
Terminal.Gui/App/Application.Screen.cs

@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui.App;
 
-public static partial class Application // Screen related stuff
+public static partial class Application // Screen related stuff; intended to hide Driver details
 {
     private static readonly object _lockScreen = new ();
     private static Rectangle? _screen;

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

@@ -242,16 +242,15 @@ public static partial class Application
         // Run State stuff
         NotifyNewRunState = null;
         NotifyStopRunState = null;
-        MouseGrabHandler = new MouseGrabHandler ();
-        // Keyboard will be lazy-initialized in ApplicationImpl on next access
+        // Mouse and Keyboard will be lazy-initialized in ApplicationImpl on next access
         Initialized = false;
 
         // Mouse
-        // Do not clear _lastMousePosition; Popover's require it to stay set with
+        // Do not clear _lastMousePosition; Popovers require it to stay set with
         // last mouse pos.
         //_lastMousePosition = null;
         CachedViewsUnderMouse.Clear ();
-        MouseEvent = null;
+        ResetMouseState ();
 
         // Keyboard events and bindings are now managed by the Keyboard instance
 

+ 186 - 148
Terminal.Gui/App/ApplicationImpl.cs

@@ -3,39 +3,52 @@ using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using Microsoft.Extensions.Logging;
-using Terminal.Gui.Drivers;
 
 namespace Terminal.Gui.App;
 
 /// <summary>
-/// Implementation of core <see cref="Application"/> methods using the modern
-/// main loop architecture with component factories for different platforms.
+///     Implementation of core <see cref="Application"/> methods using the modern
+///     main loop architecture with component factories for different platforms.
 /// </summary>
 public class ApplicationImpl : IApplication
 {
-    private readonly IComponentFactory? _componentFactory;
-    private IMainLoopCoordinator? _coordinator;
-    private string? _driverName;
-    private readonly ITimedEvents _timedEvents = new TimedEvents ();
-
     // Private static readonly Lazy instance of Application
     private static Lazy<IApplication> _lazyInstance = new (() => new ApplicationImpl ());
 
     /// <summary>
-    /// Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
-    /// Change to your own implementation by using <see cref="ChangeInstance"/> (before init).
+    ///     Creates a new instance of the Application backend.
     /// </summary>
-    public static IApplication Instance => _lazyInstance.Value;
+    public ApplicationImpl () { }
+
+    internal ApplicationImpl (IComponentFactory componentFactory)
+    {
+        _componentFactory = componentFactory;
+    }
+
+    private readonly IComponentFactory? _componentFactory;
+    private readonly ITimedEvents _timedEvents = new TimedEvents ();
+    private string? _driverName;
 
     /// <inheritdoc/>
     public ITimedEvents? TimedEvents => _timedEvents;
 
-    internal IMainLoopCoordinator? Coordinator => _coordinator;
+    private IMouse? _mouse;
 
     /// <summary>
-    /// Handles which <see cref="View"/> (if any) has captured the mouse
+    /// Handles mouse event state and processing.
     /// </summary>
-    public IMouseGrabHandler MouseGrabHandler { get; set; } = new MouseGrabHandler ();
+    public IMouse Mouse
+    {
+        get
+        {
+            if (_mouse is null)
+            {
+                _mouse = new MouseImpl { Application = this };
+            }
+            return _mouse;
+        }
+        set => _mouse = value ?? throw new ArgumentNullException (nameof (value));
+    }
 
     private IKeyboard? _keyboard;
 
@@ -48,7 +61,7 @@ public class ApplicationImpl : IApplication
         {
             if (_keyboard is null)
             {
-                _keyboard = new Keyboard { Application = this };
+                _keyboard = new KeyboardImpl { Application = this };
             }
             return _keyboard;
         }
@@ -83,6 +96,7 @@ public class ApplicationImpl : IApplication
         set => Application.Navigation = value;
     }
 
+    // TODO: Create an IViewHierarchy that encapsulates Top and TopLevels and LayoutAndDraw
     /// <inheritdoc/>
     public Toplevel? Top
     {
@@ -93,30 +107,38 @@ public class ApplicationImpl : IApplication
     /// <inheritdoc/>
     public ConcurrentStack<Toplevel> TopLevels => Application.TopLevels;
 
-    /// <inheritdoc/>
-    public void RequestStop () => Application.RequestStop ();
-
-    /// <summary>
-    /// Creates a new instance of the Application backend.
-    /// </summary>
-    public ApplicationImpl ()
+    /// <inheritdoc />
+    public void LayoutAndDraw (bool forceRedraw = false)
     {
-    }
+        List<View> tops = [.. TopLevels];
 
-    internal ApplicationImpl (IComponentFactory componentFactory)
-    {
-        _componentFactory = componentFactory;
-    }
+        if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
+        {
+            visiblePopover.SetNeedsDraw ();
+            visiblePopover.SetNeedsLayout ();
+            tops.Insert (0, visiblePopover);
+        }
 
-    /// <summary>
-    /// Change the singleton implementation, should not be called except before application
-    /// startup. This method lets you provide alternative implementations of core static gateway
-    /// methods of <see cref="Application"/>.
-    /// </summary>
-    /// <param name="newApplication"></param>
-    public static void ChangeInstance (IApplication newApplication)
-    {
-        _lazyInstance = new Lazy<IApplication> (newApplication);
+        // BUGBUG: Application.Screen needs to be moved to IApplication
+        bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Application.Screen.Size);
+
+        // BUGBUG: Application.ClearScreenNextIteration needs to be moved to IApplication
+        if (Application.ClearScreenNextIteration)
+        {
+            forceRedraw = true;
+            // BUGBUG: Application.Screen needs to be moved to IApplication
+            Application.ClearScreenNextIteration = false;
+        }
+
+        if (forceRedraw)
+        {
+            Driver?.ClearContents ();
+        }
+
+        View.SetClipToScreen ();
+        View.Draw (tops, neededLayout || forceRedraw);
+        View.SetClipToScreen ();
+        Driver?.Refresh ();
     }
 
     /// <inheritdoc/>
@@ -141,12 +163,13 @@ public class ApplicationImpl : IApplication
             _driverName = Application.ForceDriver;
         }
 
-        Debug.Assert(Application.Navigation is null);
+        Debug.Assert (Application.Navigation is null);
         Application.Navigation = new ();
 
         Debug.Assert (Application.Popover is null);
         Application.Popover = new ();
 
+        // TODO: Move this into IKeyboard and Keyboard implementation
         // Preserve existing keyboard settings if they exist
         bool hasExistingKeyboard = _keyboard is not null;
         Key existingQuitKey = _keyboard?.QuitKey ?? Key.Esc;
@@ -157,7 +180,7 @@ public class ApplicationImpl : IApplication
         Key existingPrevTabGroupKey = _keyboard?.PrevTabGroupKey ?? Key.F6.WithShift;
 
         // Reset keyboard to ensure fresh state with default bindings
-        _keyboard = new Keyboard { Application = this };
+        _keyboard = new KeyboardImpl { Application = this };
 
         // Restore previously set keys if they existed and were different from defaults
         if (hasExistingKeyboard)
@@ -181,92 +204,6 @@ public class ApplicationImpl : IApplication
         Application.MainThreadId = Thread.CurrentThread.ManagedThreadId;
     }
 
-    private void CreateDriver (string? driverName)
-    {
-        // When running unit tests, always use FakeDriver unless explicitly specified
-        if (ConsoleDriver.RunningUnitTests && 
-            string.IsNullOrEmpty (driverName) && 
-            _componentFactory is null)
-        {
-            Logging.Logger.LogDebug ("Unit test safeguard: forcing FakeDriver (RunningUnitTests=true, driverName=null, componentFactory=null)");
-            _coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
-            _coordinator.StartAsync ().Wait ();
-
-            if (Application.Driver == null)
-            {
-                throw new ("Application.Driver was null even after booting MainLoopCoordinator");
-            }
-
-            return;
-        }
-
-        PlatformID p = Environment.OSVersion.Platform;
-
-        // Check component factory type first - this takes precedence over driverName
-        bool factoryIsWindows = _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
-        bool factoryIsDotNet = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
-        bool factoryIsUnix = _componentFactory is IComponentFactory<char>;
-        bool factoryIsFake = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
-
-        // Then check driverName
-        bool nameIsWindows = driverName?.Contains ("win", StringComparison.OrdinalIgnoreCase) ?? false;
-        bool nameIsDotNet = (driverName?.Contains ("dotnet", StringComparison.OrdinalIgnoreCase) ?? false);
-        bool nameIsUnix = driverName?.Contains ("unix", StringComparison.OrdinalIgnoreCase) ?? false;
-        bool nameIsFake = driverName?.Contains ("fake", StringComparison.OrdinalIgnoreCase) ?? false;
-
-        // Decide which driver to use - component factory type takes priority
-        if (factoryIsFake || (!factoryIsWindows && !factoryIsDotNet && !factoryIsUnix && nameIsFake))
-        {
-            _coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
-        }
-        else if (factoryIsWindows || (!factoryIsDotNet && !factoryIsUnix && nameIsWindows))
-        {
-            _coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
-        }
-        else if (factoryIsDotNet || (!factoryIsWindows && !factoryIsUnix && nameIsDotNet))
-        {
-            _coordinator = CreateSubcomponents (() => new NetComponentFactory ());
-        }
-        else if (factoryIsUnix || (!factoryIsWindows && !factoryIsDotNet && nameIsUnix))
-        {
-            _coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
-        }
-        else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
-        {
-            _coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
-        }
-        else
-        {
-            _coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
-        }
-
-        _coordinator.StartAsync ().Wait ();
-
-        if (Application.Driver == null)
-        {
-            throw new ("Application.Driver was null even after booting MainLoopCoordinator");
-        }
-    }
-
-    private IMainLoopCoordinator CreateSubcomponents<T> (Func<IComponentFactory<T>> fallbackFactory)
-    {
-        ConcurrentQueue<T> inputBuffer = new ();
-        ApplicationMainLoop<T> loop = new ();
-
-        IComponentFactory<T> cf;
-
-        if (_componentFactory is IComponentFactory<T> typedFactory)
-        {
-            cf = typedFactory;
-        }
-        else
-        {
-            cf = fallbackFactory ();
-        }
-
-        return new MainLoopCoordinator<T> (_timedEvents, inputBuffer, loop, cf);
-    }
-
     /// <summary>
     ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
     ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
@@ -294,11 +231,12 @@ public class ApplicationImpl : IApplication
         if (!Application.Initialized)
         {
             // Init() has NOT been called. Auto-initialize as per interface contract.
-            Init (driver, null);
+            Init (driver);
         }
 
         T top = new ();
         Run (top, errorHandler);
+
         return top;
     }
 
@@ -317,7 +255,7 @@ public class ApplicationImpl : IApplication
 
         if (Application.Driver == null)
         {
-            throw new  InvalidOperationException ("Driver was inexplicably null when trying to Run view");
+            throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view");
         }
 
         Application.Top = view;
@@ -328,23 +266,23 @@ public class ApplicationImpl : IApplication
 
         while (Application.TopLevels.TryPeek (out Toplevel? found) && found == view && view.Running)
         {
-            if (_coordinator is null)
+            if (Coordinator is null)
             {
                 throw new ($"{nameof (IMainLoopCoordinator)} inexplicably became null during Run");
             }
 
-            _coordinator.RunIteration ();
+            Coordinator.RunIteration ();
         }
 
-        Logging.Information ($"Run - Calling End");
+        Logging.Information ("Run - Calling End");
         Application.End (rs);
     }
 
     /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
     public void Shutdown ()
     {
-        _coordinator?.Stop ();
-        
+        Coordinator?.Stop ();
+
         bool wasInitialized = Application.Initialized;
         Application.ResetState ();
         ConfigurationManager.PrintJsonErrors ();
@@ -360,10 +298,10 @@ public class ApplicationImpl : IApplication
         _lazyInstance = new (() => new ApplicationImpl ());
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void RequestStop (Toplevel? top)
     {
-        Logging.Logger.LogInformation ($"RequestStop '{(top is {} ? top : "null")}'");
+        Logging.Logger.LogInformation ($"RequestStop '{(top is { } ? top : "null")}'");
 
         top ??= Application.Top;
 
@@ -383,38 +321,138 @@ public class ApplicationImpl : IApplication
         top.Running = false;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
+    public void RequestStop () => Application.RequestStop ();
+
+
+    /// <inheritdoc/>
     public void Invoke (Action action)
     {
         // If we are already on the main UI thread
         if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId)
         {
             action ();
+
             return;
         }
 
-        _timedEvents.Add (TimeSpan.Zero,
-                              () =>
-                              {
-                                  action ();
-                                  return false;
-                              }
-                             );
+        _timedEvents.Add (
+                          TimeSpan.Zero,
+                          () =>
+                          {
+                              action ();
+
+                              return false;
+                          }
+                         );
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public bool IsLegacy => false;
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.Add (time, callback); }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public bool RemoveTimeout (object token) { return _timedEvents.Remove (token); }
 
-    /// <inheritdoc />
-    public void LayoutAndDraw (bool forceDraw)
+    /// <summary>
+    ///     Change the singleton implementation, should not be called except before application
+    ///     startup. This method lets you provide alternative implementations of core static gateway
+    ///     methods of <see cref="Application"/>.
+    /// </summary>
+    /// <param name="newApplication"></param>
+    public static void ChangeInstance (IApplication newApplication) { _lazyInstance = new (newApplication); }
+
+    /// <summary>
+    ///     Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
+    ///     Change to your own implementation by using <see cref="ChangeInstance"/> (before init).
+    /// </summary>
+    public static IApplication Instance => _lazyInstance.Value;
+
+    internal IMainLoopCoordinator? Coordinator { get; private set; }
+
+    private void CreateDriver (string? driverName)
+    {
+        // When running unit tests, always use FakeDriver unless explicitly specified
+        if (ConsoleDriver.RunningUnitTests && string.IsNullOrEmpty (driverName) && _componentFactory is null)
+        {
+            Logging.Logger.LogDebug ("Unit test safeguard: forcing FakeDriver (RunningUnitTests=true, driverName=null, componentFactory=null)");
+            Coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
+            Coordinator.StartAsync ().Wait ();
+
+            if (Application.Driver == null)
+            {
+                throw new ("Application.Driver was null even after booting MainLoopCoordinator");
+            }
+
+            return;
+        }
+
+        PlatformID p = Environment.OSVersion.Platform;
+
+        // Check component factory type first - this takes precedence over driverName
+        bool factoryIsWindows = _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
+        bool factoryIsDotNet = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
+        bool factoryIsUnix = _componentFactory is IComponentFactory<char>;
+        bool factoryIsFake = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
+
+        // Then check driverName
+        bool nameIsWindows = driverName?.Contains ("win", StringComparison.OrdinalIgnoreCase) ?? false;
+        bool nameIsDotNet = driverName?.Contains ("dotnet", StringComparison.OrdinalIgnoreCase) ?? false;
+        bool nameIsUnix = driverName?.Contains ("unix", StringComparison.OrdinalIgnoreCase) ?? false;
+        bool nameIsFake = driverName?.Contains ("fake", StringComparison.OrdinalIgnoreCase) ?? false;
+
+        // Decide which driver to use - component factory type takes priority
+        if (factoryIsFake || (!factoryIsWindows && !factoryIsDotNet && !factoryIsUnix && nameIsFake))
+        {
+            Coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
+        }
+        else if (factoryIsWindows || (!factoryIsDotNet && !factoryIsUnix && nameIsWindows))
+        {
+            Coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
+        }
+        else if (factoryIsDotNet || (!factoryIsWindows && !factoryIsUnix && nameIsDotNet))
+        {
+            Coordinator = CreateSubcomponents (() => new NetComponentFactory ());
+        }
+        else if (factoryIsUnix || (!factoryIsWindows && !factoryIsDotNet && nameIsUnix))
+        {
+            Coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
+        }
+        else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        {
+            Coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
+        }
+        else
+        {
+            Coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
+        }
+
+        Coordinator.StartAsync ().Wait ();
+
+        if (Application.Driver == null)
+        {
+            throw new ("Application.Driver was null even after booting MainLoopCoordinator");
+        }
+    }
+
+    private IMainLoopCoordinator CreateSubcomponents<T> (Func<IComponentFactory<T>> fallbackFactory)
     {
-        Application.Top?.SetNeedsDraw();
-        Application.Top?.SetNeedsLayout ();
+        ConcurrentQueue<T> inputBuffer = new ();
+        ApplicationMainLoop<T> loop = new ();
+
+        IComponentFactory<T> cf;
+
+        if (_componentFactory is IComponentFactory<T> typedFactory)
+        {
+            cf = typedFactory;
+        }
+        else
+        {
+            cf = fallbackFactory ();
+        }
+
+        return new MainLoopCoordinator<T> (_timedEvents, inputBuffer, loop, cf);
     }
 }

+ 69 - 58
Terminal.Gui/App/IApplication.cs

@@ -4,26 +4,28 @@ using System.Diagnostics.CodeAnalysis;
 namespace Terminal.Gui.App;
 
 /// <summary>
-/// Interface for instances that provide backing functionality to static
-/// gateway class <see cref="Application"/>.
+///     Interface for instances that provide backing functionality to static
+///     gateway class <see cref="Application"/>.
 /// </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>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>
-    /// Handles grabbing the mouse (only a single <see cref="View"/> can grab the mouse at once).
+    /// Handles keyboard input and key bindings at the Application level.
     /// </summary>
-    IMouseGrabHandler MouseGrabHandler { get; set; }
+    IKeyboard Keyboard { get; set; }
 
     /// <summary>
-    /// Handles keyboard input and key bindings at the Application level.
+    ///     Handles mouse event state and processing.
     /// </summary>
-    IKeyboard Keyboard { get; set; }
+    IMouse Mouse { get; set; }
 
     /// <summary>Gets or sets the console driver being used.</summary>
     IConsoleDriver? Driver { get; set; }
@@ -46,9 +48,16 @@ public interface IApplication
     /// <summary>Requests that the application stop running.</summary>
     void RequestStop ();
 
-    /// <summary>Forces all views to be laid out and drawn.</summary>
-    /// <param name="clearScreen">If true, clears the screen before drawing.</param>
-    void LayoutAndDraw (bool clearScreen = false);
+    /// <summary>
+    ///     Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that
+    ///     need to be laid out (see <see cref="View.NeedsLayout"/>) will be laid out.
+    ///     Only Views that need to be drawn (see <see cref="View.NeedsDraw"/>) will be drawn.
+    /// </summary>
+    /// <param name="forceRedraw">
+    ///     If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and
+    ///     should only be overriden for testing.
+    /// </param>
+    public void LayoutAndDraw (bool forceRedraw = false);
 
     /// <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>
@@ -82,6 +91,39 @@ public interface IApplication
     [RequiresDynamicCode ("AOT")]
     public void Init (IConsoleDriver? driver = null, string? driverName = null);
 
+    /// <summary>Runs <paramref name="action"/> on the main UI loop thread</summary>
+    /// <param name="action">the action to be invoked on the main processing thread.</param>
+    void Invoke (Action action);
+
+    /// <summary>
+    ///     <see langword="true"/> if implementation is 'old'. <see langword="false"/> if implementation
+    ///     is cutting edge.
+    /// </summary>
+    bool IsLegacy { get; }
+
+    /// <summary>Removes a previously scheduled timeout</summary>
+    /// <remarks>The token parameter is the value returned by <see cref="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.
+    /// </returns>
+    bool RemoveTimeout (object token);
+
+    /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
+    /// <param name="top">The <see cref="Toplevel"/> to stop.</param>
+    /// <remarks>
+    ///     <para>This will cause <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to return.</para>
+    ///     <para>
+    ///         Calling <see cref="RequestStop(Toplevel)"/> is equivalent to setting the <see cref="Toplevel.Running"/>
+    ///         property on the currently running <see cref="Toplevel"/> to false.
+    ///     </para>
+    /// </remarks>
+    void RequestStop (Toplevel? top);
 
     /// <summary>
     ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
@@ -126,7 +168,7 @@ public interface IApplication
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
     public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
-        where T : Toplevel, new ();
+        where T : Toplevel, new();
 
     /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
     /// <remarks>
@@ -140,24 +182,28 @@ public interface IApplication
     ///     </para>
     ///     <para>
     ///         Calling <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
-    ///         <see cref="Application.Begin(Toplevel)"/>, followed by <see cref="Application.RunLoop(RunState)"/>, and then calling
+    ///         <see cref="Application.Begin(Toplevel)"/>, followed by <see cref="Application.RunLoop(RunState)"/>, and then
+    ///         calling
     ///         <see cref="Application.End(RunState)"/>.
     ///     </para>
     ///     <para>
     ///         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 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
+    ///     <para>
+    ///         When using <see cref="Run{T}"/> or
     ///         <see cref="Run(System.Func{System.Exception,bool},IConsoleDriver)"/>
     ///         <see cref="Init"/> will be called automatically.
     ///     </para>
     ///     <para>
     ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
     ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
-    ///         returns <see langword="true"/> the <see cref="Application.RunLoop(RunState)"/> will resume; otherwise this method will
+    ///         returns <see langword="true"/> the <see cref="Application.RunLoop(RunState)"/> will resume; otherwise this
+    ///         method will
     ///         exit.
     ///     </para>
     /// </remarks>
@@ -177,44 +223,9 @@ public interface IApplication
     /// </remarks>
     public void Shutdown ();
 
-    /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
-    /// <param name="top">The <see cref="Toplevel"/> to stop.</param>
-    /// <remarks>
-    ///     <para>This will cause <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to return.</para>
-    ///     <para>
-    ///         Calling <see cref="RequestStop(Toplevel)"/> is equivalent to setting the <see cref="Toplevel.Running"/>
-    ///         property on the currently running <see cref="Toplevel"/> to false.
-    ///     </para>
-    /// </remarks>
-    void RequestStop (Toplevel? top);
-
-    /// <summary>Runs <paramref name="action"/> on the main UI loop thread</summary>
-    /// <param name="action">the action to be invoked on the main processing thread.</param>
-    void Invoke (Action action);
-
     /// <summary>
-    /// <see langword="true"/> if implementation is 'old'. <see langword="false"/> if implementation
-    /// is cutting edge.
+    ///     Handles recurring events. These are invoked on the main UI thread - allowing for
+    ///     safe updates to <see cref="View"/> instances.
     /// </summary>
-    bool IsLegacy { get; }
-    
-    /// <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 <see cref="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.</returns>
-    bool RemoveTimeout (object token);
-}
+    ITimedEvents? TimedEvents { get; }
+}

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

@@ -11,7 +11,7 @@ namespace Terminal.Gui.App;
 ///         See <see cref="IKeyboard"/> for usage details.
 ///     </para>
 /// </summary>
-internal class Keyboard : IKeyboard
+internal class KeyboardImpl : IKeyboard
 {
     private Key _quitKey = Key.Esc; // Resources/config.json overrides
     private Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrides
@@ -106,7 +106,7 @@ internal class Keyboard : IKeyboard
     /// <summary>
     ///     Initializes keyboard bindings.
     /// </summary>
-    public Keyboard ()
+    public KeyboardImpl ()
     {
         AddKeyBindings ();
     }

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

@@ -150,7 +150,7 @@ public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
         {
             bool needsDrawOrLayout = AnySubViewsNeedDrawn (Application.Popover?.GetActivePopover () as View)
                                      || AnySubViewsNeedDrawn (Application.Top)
-                                     || (Application.MouseGrabHandler.MouseGrabView != null && AnySubViewsNeedDrawn (Application.MouseGrabHandler.MouseGrabView));
+                                     || (Application.Mouse.MouseGrabView != null && AnySubViewsNeedDrawn (Application.Mouse.MouseGrabView));
 
             bool sizeChanged = WindowSizeMonitor.Poll ();
 

+ 79 - 0
Terminal.Gui/App/Mouse/IMouse.cs

@@ -0,0 +1,79 @@
+#nullable enable
+using System.ComponentModel;
+
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     Defines a contract for mouse event handling and state management in a Terminal.Gui application.
+///     <para>
+///         This interface allows for decoupling of mouse-related functionality from the static <see cref="Application"/> class,
+///         enabling better testability and parallel test execution.
+///     </para>
+/// </summary>
+public interface IMouse : IMouseGrabHandler
+{
+    /// <summary>
+    /// Sets the application instance that this mouse handler is associated with.
+    /// This provides access to application state without coupling to static Application class.
+    /// </summary>
+    IApplication? Application { get; set; }
+
+    /// <summary>
+    ///     Gets or sets the last known position of the mouse.
+    /// </summary>
+    Point? LastMousePosition { get; set; }
+
+    /// <summary>
+    ///     Gets the most recent position of the mouse.
+    /// </summary>
+    Point? GetLastMousePosition ();
+
+    /// <summary>
+    ///     Gets or sets whether the mouse is disabled. The mouse is enabled by default.
+    /// </summary>
+    bool IsMouseDisabled { get; set; }
+
+    /// <summary>
+    ///     Gets the list of non-<see cref="ViewportSettingsFlags.TransparentMouse"/> views that are currently under the mouse.
+    /// </summary>
+    List<View?> CachedViewsUnderMouse { get; }
+
+    /// <summary>
+    ///     Raised when a mouse event occurs. Can be cancelled by setting <see cref="HandledEventArgs.Handled"/> to <see langword="true"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         <see cref="MouseEventArgs.ScreenPosition"/> coordinates are screen-relative.
+    ///     </para>
+    ///     <para>
+    ///         <see cref="MouseEventArgs.View"/> will be the deepest view under the mouse.
+    ///     </para>
+    ///     <para>
+    ///         <see cref="MouseEventArgs.Position"/> coordinates are view-relative. Only valid if <see cref="MouseEventArgs.View"/> is set.
+    ///     </para>
+    ///     <para>
+    ///         Use this even to handle mouse events at the application level, before View-specific handling.
+    ///     </para>
+    /// </remarks>
+    event EventHandler<MouseEventArgs>? MouseEvent;
+
+    /// <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.
+    /// </summary>
+    /// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
+    /// <param name="mouseEvent">The mouse event with coordinates relative to the screen.</param>
+    void RaiseMouseEvent (MouseEventArgs mouseEvent);
+
+    /// <summary>
+    ///     INTERNAL: Raises the MouseEnter and MouseLeave events for the views that are under the mouse.
+    /// </summary>
+    /// <param name="screenPosition">The position of the mouse.</param>
+    /// <param name="currentViewsUnderMouse">The most recent result from GetViewsUnderLocation().</param>
+    void RaiseMouseEnterLeaveEvents (Point screenPosition, List<View?> currentViewsUnderMouse);
+
+    /// <summary>
+    ///     INTERNAL: Resets mouse state, clearing event handlers and cached views.
+    /// </summary>
+    void ResetState ();
+}

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

@@ -84,4 +84,12 @@ public interface IMouseGrabHandler
     ///     Releases the mouse grab, so mouse events will be routed to the view under the mouse pointer.
     /// </summary>
     public void UngrabMouse ();
+
+    /// <summary>
+    ///     Handles mouse grab logic for a mouse event.
+    /// </summary>
+    /// <param name="deepestViewUnderMouse">The deepest view under the mouse.</param>
+    /// <param name="mouseEvent">The mouse event to handle.</param>
+    /// <returns><see langword="true"/> if the event was handled by the grab handler; otherwise <see langword="false"/>.</returns>
+    bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent);
 }

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

@@ -115,4 +115,45 @@ internal class MouseGrabHandler : IMouseGrabHandler
 
         UnGrabbedMouse?.Invoke (view, new (view));
     }
+
+    /// <inheritdoc/>
+    public bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent)
+    {
+        if (MouseGrabView is { })
+        {
+#if DEBUG_IDISPOSABLE
+            if (View.EnableDebugIDisposableAsserts && MouseGrabView.WasDisposed)
+            {
+                throw new ObjectDisposedException (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);
+
+            var viewRelativeMouseEvent = new MouseEventArgs
+            {
+                Position = frameLoc,
+                Flags = mouseEvent.Flags,
+                ScreenPosition = mouseEvent.ScreenPosition,
+                View = deepestViewUnderMouse ?? MouseGrabView
+            };
+
+            //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
+            if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
+            {
+                return true;
+            }
+
+            // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+            if (MouseGrabView is null && deepestViewUnderMouse is Adornment)
+            {
+                // The view that grabbed the mouse has been disposed
+                return true;
+            }
+        }
+
+        return false;
+    }
 }

+ 393 - 0
Terminal.Gui/App/Mouse/MouseImpl.cs

@@ -0,0 +1,393 @@
+#nullable enable
+using System.ComponentModel;
+
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     INTERNAL: Implements <see cref="IMouse"/> to manage mouse event handling and state.
+///     <para>
+///         This class holds all mouse-related state that was previously in the static <see cref="Application"/> class,
+///         enabling better testability and parallel test execution.
+///     </para>
+/// </summary>
+internal class MouseImpl : IMouse
+{
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="MouseImpl"/> class.
+    /// </summary>
+    public MouseImpl () { }
+
+    /// <inheritdoc/>
+    public IApplication? Application { get; set; }
+
+    /// <inheritdoc/>
+    public Point? LastMousePosition { get; set; }
+
+    /// <inheritdoc/>
+    public Point? GetLastMousePosition () { return LastMousePosition; }
+
+    /// <inheritdoc/>
+    public bool IsMouseDisabled { get; set; }
+
+    /// <inheritdoc/>
+    public List<View?> CachedViewsUnderMouse { get; } = [];
+
+    /// <inheritdoc/>
+    public event EventHandler<MouseEventArgs>? MouseEvent;
+
+    // Mouse grab functionality merged from MouseGrabHandler
+
+    /// <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 RaiseMouseEvent (MouseEventArgs mouseEvent)
+    {
+        if (Application?.Initialized is true)
+        {
+            // LastMousePosition is only set if the application is initialized.
+            LastMousePosition = mouseEvent.ScreenPosition;
+        }
+
+        if (IsMouseDisabled)
+        {
+            return;
+        }
+
+        // The position of the mouse is the same as the screen position at the application level.
+        //Debug.Assert (mouseEvent.Position == mouseEvent.ScreenPosition);
+        mouseEvent.Position = mouseEvent.ScreenPosition;
+
+        List<View?> currentViewsUnderMouse = View.GetViewsUnderLocation (mouseEvent.ScreenPosition, ViewportSettingsFlags.TransparentMouse);
+
+        View? deepestViewUnderMouse = currentViewsUnderMouse.LastOrDefault ();
+
+        if (deepestViewUnderMouse is { })
+        {
+#if DEBUG_IDISPOSABLE
+            if (View.EnableDebugIDisposableAsserts && deepestViewUnderMouse.WasDisposed)
+            {
+                throw new ObjectDisposedException (deepestViewUnderMouse.GetType ().FullName);
+            }
+#endif
+            mouseEvent.View = deepestViewUnderMouse;
+        }
+
+        MouseEvent?.Invoke (null, mouseEvent);
+
+        if (mouseEvent.Handled)
+        {
+            return;
+        }
+
+        // Dismiss the Popover if the user presses mouse outside of it
+        if (mouseEvent.IsPressed
+            && Application?.Popover?.GetActivePopover () as View is { Visible: true } visiblePopover
+            && View.IsInHierarchy (visiblePopover, deepestViewUnderMouse, includeAdornments: true) is false)
+        {
+            ApplicationPopover.HideWithQuitCommand (visiblePopover);
+
+            // Recurse once so the event can be handled below the popover
+            RaiseMouseEvent (mouseEvent);
+
+            return;
+        }
+
+        if (HandleMouseGrab (deepestViewUnderMouse, mouseEvent))
+        {
+            return;
+        }
+
+        // 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)
+        {
+            return;
+        }
+
+        // if the mouse is outside the Application.Top or Application.Popover hierarchy, we don't want to
+        // send the mouse event to the deepest view under the mouse.
+        if (!View.IsInHierarchy (Application?.Top, deepestViewUnderMouse, true) && !View.IsInHierarchy (Application?.Popover?.GetActivePopover () as View, deepestViewUnderMouse, true))
+        {
+            return;
+        }
+
+        // Create a view-relative mouse event to send to the view that is under the mouse.
+        MouseEventArgs viewMouseEvent;
+
+        if (deepestViewUnderMouse is Adornment adornment)
+        {
+            Point frameLoc = adornment.ScreenToFrame (mouseEvent.ScreenPosition);
+
+            viewMouseEvent = new ()
+            {
+                Position = frameLoc,
+                Flags = mouseEvent.Flags,
+                ScreenPosition = mouseEvent.ScreenPosition,
+                View = deepestViewUnderMouse
+            };
+        }
+        else if (deepestViewUnderMouse.ViewportToScreen (Rectangle.Empty with { Size = deepestViewUnderMouse.Viewport.Size }).Contains (mouseEvent.ScreenPosition))
+        {
+            Point viewportLocation = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);
+
+            viewMouseEvent = new ()
+            {
+                Position = viewportLocation,
+                Flags = mouseEvent.Flags,
+                ScreenPosition = mouseEvent.ScreenPosition,
+                View = deepestViewUnderMouse
+            };
+        }
+        else
+        {
+            // The mouse was outside any View's Viewport.
+            // Debug.Fail ("This should never happen. If it does please file an Issue!!");
+
+            return;
+        }
+
+        RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse);
+
+        while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabView is not { })
+        {
+            if (deepestViewUnderMouse is Adornment adornmentView)
+            {
+                deepestViewUnderMouse = adornmentView.Parent?.SuperView;
+            }
+            else
+            {
+                deepestViewUnderMouse = deepestViewUnderMouse.SuperView;
+            }
+
+            if (deepestViewUnderMouse is null)
+            {
+                break;
+            }
+
+            Point boundsPoint = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);
+
+            viewMouseEvent = new ()
+            {
+                Position = boundsPoint,
+                Flags = mouseEvent.Flags,
+                ScreenPosition = mouseEvent.ScreenPosition,
+                View = deepestViewUnderMouse
+            };
+        }
+    }
+
+    /// <inheritdoc/>
+    public void RaiseMouseEnterLeaveEvents (Point screenPosition, List<View?> currentViewsUnderMouse)
+    {
+        // Tell any views that are no longer under the mouse that the mouse has left
+        List<View?> viewsToLeave = CachedViewsUnderMouse.Where (v => v is { } && !currentViewsUnderMouse.Contains (v)).ToList ();
+
+        foreach (View? view in viewsToLeave)
+        {
+            if (view is null)
+            {
+                continue;
+            }
+
+            view.NewMouseLeaveEvent ();
+            CachedViewsUnderMouse.Remove (view);
+        }
+
+        // Tell any views that are now under the mouse that the mouse has entered and add them to the list
+        foreach (View? view in currentViewsUnderMouse)
+        {
+            if (view is null)
+            {
+                continue;
+            }
+
+            if (CachedViewsUnderMouse.Contains (view))
+            {
+                continue;
+            }
+
+            CachedViewsUnderMouse.Add (view);
+            var raise = false;
+
+            if (view is Adornment { Parent: { } } adornmentView)
+            {
+                Point superViewLoc = adornmentView.Parent.SuperView?.ScreenToViewport (screenPosition) ?? screenPosition;
+                raise = adornmentView.Contains (superViewLoc);
+            }
+            else
+            {
+                Point superViewLoc = view.SuperView?.ScreenToViewport (screenPosition) ?? screenPosition;
+                raise = view.Contains (superViewLoc);
+            }
+
+            if (!raise)
+            {
+                continue;
+            }
+
+            CancelEventArgs eventArgs = new System.ComponentModel.CancelEventArgs ();
+            bool? cancelled = view.NewMouseEnterEvent (eventArgs);
+
+            if (cancelled is true || eventArgs.Cancel)
+            {
+                break;
+            }
+        }
+    }
+
+    /// <inheritdoc/>
+    public void ResetState ()
+    {
+        // Do not clear LastMousePosition; Popover's require it to stay set with last mouse pos.
+        CachedViewsUnderMouse.Clear ();
+        MouseEvent = null;
+    }
+
+    // Mouse grab functionality merged from MouseGrabHandler
+
+    /// <inheritdoc/>
+    public void GrabMouse (View? view)
+    {
+        if (view is null || RaiseGrabbingMouseEvent (view))
+        {
+            return;
+        }
+
+        RaiseGrabbedMouseEvent (view);
+
+        // MouseGrabView is 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;
+        }
+
+        GrabMouseEventArgs evArgs = new (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;
+        }
+
+        GrabMouseEventArgs evArgs = new (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));
+    }
+
+    /// <summary>
+    ///     Handles mouse grab logic for a mouse event.
+    /// </summary>
+    /// <param name="deepestViewUnderMouse">The deepest view under the mouse.</param>
+    /// <param name="mouseEvent">The mouse event to handle.</param>
+    /// <returns><see langword="true"/> if the event was handled by the grab handler; otherwise <see langword="false"/>.</returns>
+    public bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent)
+    {
+        if (MouseGrabView is { })
+        {
+#if DEBUG_IDISPOSABLE
+            if (View.EnableDebugIDisposableAsserts && MouseGrabView.WasDisposed)
+            {
+                throw new ObjectDisposedException (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);
+
+            MouseEventArgs viewRelativeMouseEvent = new ()
+            {
+                Position = frameLoc,
+                Flags = mouseEvent.Flags,
+                ScreenPosition = mouseEvent.ScreenPosition,
+                View = deepestViewUnderMouse ?? MouseGrabView
+            };
+
+            //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
+            if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
+            {
+                return true;
+            }
+
+            // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+            if (MouseGrabView is null && deepestViewUnderMouse is Adornment)
+            {
+                // The view that grabbed the mouse has been disposed
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

+ 1 - 3
Terminal.Gui/Drivers/IConsoleDriver.cs

@@ -4,9 +4,7 @@ namespace Terminal.Gui.Drivers;
 
 /// <summary>Base interface for Terminal.Gui ConsoleDriver implementations.</summary>
 /// <remarks>
-///     There are currently four implementations: - <see cref="UnixDriver"/> (for Unix and Mac) -
-///     <see cref="WindowsDriver"/> - <see cref="DotNetDriver"/> that uses the .NET Console API - <see cref="FakeConsole"/>
-///     for unit testing.
+///     There are currently four implementations: UnixDriver, WindowsDriver, DotNetDriver, and FakeDriver
 /// </remarks>
 public interface IConsoleDriver
 {

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

@@ -431,9 +431,9 @@ public partial class Border
 
         Application.MouseEvent -= ApplicationOnMouseEvent;
 
-        if (Application.MouseGrabHandler.MouseGrabView == this && _dragPosition.HasValue)
+        if (Application.Mouse.MouseGrabView == this && _dragPosition.HasValue)
         {
-            Application.MouseGrabHandler.UngrabMouse ();
+            Application.Mouse.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.MouseGrabHandler.GrabMouse (this);
+                Application.Mouse.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.MouseGrabHandler.MouseGrabView == this)
+        if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && Application.Mouse.MouseGrabView == this)
         {
             if (_dragPosition.HasValue)
             {
@@ -523,7 +523,7 @@ public partial class Border
         if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue)
         {
             _dragPosition = null;
-            Application.MouseGrabHandler.UngrabMouse ();
+            Application.Mouse.UngrabMouse ();
 
             EndArrangeMode ();
 
@@ -763,7 +763,7 @@ public partial class Border
 
     private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e)
     {
-        if (Application.MouseGrabHandler.MouseGrabView == this && _dragPosition.HasValue)
+        if (Application.Mouse.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.MouseGrabHandler.MouseGrabView == this && _dragPosition.HasValue)
+        if (Application.Mouse.MouseGrabView == this && _dragPosition.HasValue)
         {
             e.Cancel = true;
         }
@@ -784,8 +784,8 @@ public partial class Border
     /// <inheritdoc/>
     protected override void Dispose (bool disposing)
     {
-        Application.MouseGrabHandler.GrabbingMouse -= Application_GrabbingMouse;
-        Application.MouseGrabHandler.UnGrabbingMouse -= Application_UnGrabbingMouse;
+        Application.Mouse.GrabbingMouse -= Application_GrabbingMouse;
+        Application.Mouse.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.MouseGrabHandler.GrabbingMouse += Application_GrabbingMouse;
-        Application.MouseGrabHandler.UnGrabbingMouse += Application_UnGrabbingMouse;
+        Application.Mouse.GrabbingMouse += Application_GrabbingMouse;
+        Application.Mouse.UnGrabbingMouse += Application_UnGrabbingMouse;
 
         ThicknessChanged += OnThicknessChanged;
     }

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

@@ -16,7 +16,7 @@ public partial class View // Mouse APIs
 
     private void SetupMouse ()
     {
-        MouseHeldDown = new MouseHeldDown (this, Application.TimedEvents,Application.MouseGrabHandler);
+        MouseHeldDown = new MouseHeldDown (this, Application.TimedEvents,Application.Mouse);
         MouseBindings = new ();
 
         // TODO: Should the default really work with any button or just button1?
@@ -375,7 +375,7 @@ public partial class View // Mouse APIs
 
         if (mouseEvent.IsReleased)
         {
-            if (Application.MouseGrabHandler.MouseGrabView == this)
+            if (Application.Mouse.MouseGrabView == this)
             {
                 //Logging.Debug ($"{Id} - {MouseState}");
                 MouseState &= ~MouseState.Pressed;
@@ -407,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.MouseGrabHandler.MouseGrabView != this)
+            if (Application.Mouse.MouseGrabView != this)
             {
-                Application.MouseGrabHandler.GrabMouse (this);
+                Application.Mouse.GrabMouse (this);
 
                 if (!HasFocus && CanFocus)
                 {
@@ -541,10 +541,10 @@ public partial class View // Mouse APIs
     {
         mouseEvent.Handled = false;
 
-        if (Application.MouseGrabHandler.MouseGrabView == this && mouseEvent.IsSingleClicked)
+        if (Application.Mouse.MouseGrabView == this && mouseEvent.IsSingleClicked)
         {
             // We're grabbed. Clicked event comes after the last Release. This is our signal to ungrab
-            Application.MouseGrabHandler.UngrabMouse ();
+            Application.Mouse.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

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

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

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

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

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

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

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

@@ -19,7 +19,7 @@ internal sealed class Menu : View
         }
 
         Application.MouseEvent += Application_RootMouseEvent;
-        Application.MouseGrabHandler.UnGrabbedMouse += Application_UnGrabbedMouse;
+        Application.Mouse.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.MouseGrabHandler.UngrabMouse ();
+        Application.Mouse.UngrabMouse ();
         _host.CloseAllMenus ();
         Application.LayoutAndDraw (true);
 
@@ -238,7 +238,7 @@ internal sealed class Menu : View
         }
 
         Application.MouseEvent -= Application_RootMouseEvent;
-        Application.MouseGrabHandler.UnGrabbedMouse -= Application_UnGrabbedMouse;
+        Application.Mouse.UnGrabbedMouse -= Application_UnGrabbedMouse;
         base.Dispose (disposing);
     }
 
@@ -535,7 +535,7 @@ internal sealed class Menu : View
 
     private void CloseAllMenus ()
     {
-        Application.MouseGrabHandler.UngrabMouse ();
+        Application.Mouse.UngrabMouse ();
         _host.CloseAllMenus ();
     }
 

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

@@ -442,12 +442,12 @@ public class MenuBar : View, IDesignable
 
         if (_isContextMenuLoading)
         {
-            Application.MouseGrabHandler.GrabMouse (_openMenu);
+            Application.Mouse.GrabMouse (_openMenu);
             _isContextMenuLoading = false;
         }
         else
         {
-            Application.MouseGrabHandler.GrabMouse (this);
+            Application.Mouse.GrabMouse (this);
         }
     }
 
@@ -524,16 +524,16 @@ public class MenuBar : View, IDesignable
 
         SetNeedsDraw ();
 
-        if (Application.MouseGrabHandler.MouseGrabView is { } && Application.MouseGrabHandler.MouseGrabView is MenuBar && Application.MouseGrabHandler.MouseGrabView != this)
+        if (Application.Mouse.MouseGrabView is { } && Application.Mouse.MouseGrabView is MenuBar && Application.Mouse.MouseGrabView != this)
         {
-            var menuBar = Application.MouseGrabHandler.MouseGrabView as MenuBar;
+            var menuBar = Application.Mouse.MouseGrabView as MenuBar;
 
             if (menuBar!.IsMenuOpen)
             {
                 menuBar.CleanUp ();
             }
         }
-        Application.MouseGrabHandler.UngrabMouse ();
+        Application.Mouse.UngrabMouse ();
         _isCleaning = false;
     }
 
@@ -556,7 +556,7 @@ public class MenuBar : View, IDesignable
                 _selected = -1;
             }
 
-            Application.MouseGrabHandler.UngrabMouse ();
+            Application.Mouse.UngrabMouse ();
         }
 
         if (OpenCurrentMenu is { })
@@ -622,9 +622,9 @@ public class MenuBar : View, IDesignable
                     _previousFocused.SetFocus ();
                 }
 
-                if (Application.MouseGrabHandler.MouseGrabView == _openMenu)
+                if (Application.Mouse.MouseGrabView == _openMenu)
                 {
-                    Application.MouseGrabHandler.UngrabMouse ();
+                    Application.Mouse.UngrabMouse ();
                 }
                 _openMenu?.Dispose ();
                 _openMenu = null;
@@ -652,9 +652,9 @@ public class MenuBar : View, IDesignable
                     if (OpenCurrentMenu is { })
                     {
                         SuperView?.Remove (OpenCurrentMenu);
-                        if (Application.MouseGrabHandler.MouseGrabView == OpenCurrentMenu)
+                        if (Application.Mouse.MouseGrabView == OpenCurrentMenu)
                         {
-                            Application.MouseGrabHandler.UngrabMouse ();
+                            Application.Mouse.UngrabMouse ();
                         }
                         OpenCurrentMenu.Dispose ();
                         OpenCurrentMenu = null;
@@ -845,9 +845,9 @@ public class MenuBar : View, IDesignable
                 if (_openMenu is { })
                 {
                     SuperView?.Remove (_openMenu);
-                    if (Application.MouseGrabHandler.MouseGrabView == _openMenu)
+                    if (Application.Mouse.MouseGrabView == _openMenu)
                     {
-                        Application.MouseGrabHandler.UngrabMouse ();
+                        Application.Mouse.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.MouseGrabHandler.GrabMouse (OpenCurrentMenu);
+                        Application.Mouse.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.MouseGrabHandler.MouseGrabView == item)
+                if (Application.Mouse.MouseGrabView == item)
                 {
-                    Application.MouseGrabHandler.UngrabMouse ();
+                    Application.Mouse.UngrabMouse ();
                 }
                 item.Dispose ();
             }
@@ -1137,7 +1137,7 @@ public class MenuBar : View, IDesignable
             return false;
         }
 
-        Application.MouseGrabHandler.UngrabMouse ();
+        Application.Mouse.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.MouseGrabHandler.MouseGrabView == menu)
+            if (Application.Mouse.MouseGrabView == menu)
             {
-                Application.MouseGrabHandler.UngrabMouse ();
+                Application.Mouse.UngrabMouse ();
             }
             menu.Dispose ();
         }
         else
         {
-            Application.MouseGrabHandler.GrabMouse (this);
+            Application.Mouse.GrabMouse (this);
             _selected = i;
             OpenMenu (i);
 
@@ -1280,9 +1280,9 @@ public class MenuBar : View, IDesignable
                 SuperView!.Remove (menu);
                 _openSubMenu.Remove (menu);
 
-                if (Application.MouseGrabHandler.MouseGrabView == menu)
+                if (Application.Mouse.MouseGrabView == menu)
                 {
-                    Application.MouseGrabHandler.GrabMouse (this);
+                    Application.Mouse.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.MouseGrabHandler.MouseGrabView == menu)
+                            if (Application.Mouse.MouseGrabView == menu)
                             {
-                                Application.MouseGrabHandler.UngrabMouse ();
+                                Application.Mouse.UngrabMouse ();
                             }
 
                             menu.Dispose ();
@@ -1535,7 +1535,7 @@ public class MenuBar : View, IDesignable
 
     internal bool HandleGrabView (MouseEventArgs me, View current)
     {
-        if (Application.MouseGrabHandler.MouseGrabView is { })
+        if (Application.Mouse.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.MouseGrabHandler.GrabMouse (me.View);
+                        Application.Mouse.GrabMouse (me.View);
                     }
                     else
                     {
@@ -1556,10 +1556,10 @@ public class MenuBar : View, IDesignable
                     }
                 }
 
-                if (Application.MouseGrabHandler.MouseGrabView != me.View)
+                if (Application.Mouse.MouseGrabView != me.View)
                 {
                     View v = me.View;
-                    Application.MouseGrabHandler.GrabMouse (v);
+                    Application.Mouse.GrabMouse (v);
 
                     return true;
                 }
@@ -1567,7 +1567,7 @@ public class MenuBar : View, IDesignable
                 if (me.View != current)
                 {
                     View v = me.View;
-                    Application.MouseGrabHandler.GrabMouse (v);
+                    Application.Mouse.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.MouseGrabHandler.UngrabMouse ();
+                Application.Mouse.UngrabMouse ();
 
                 if (IsMenuOpen)
                 {
@@ -1625,11 +1625,11 @@ public class MenuBar : View, IDesignable
                                           MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
                                          )))
         {
-            Application.MouseGrabHandler.GrabMouse (current);
+            Application.Mouse.GrabMouse (current);
         }
         else if (IsMenuOpen && (me.View is MenuBar || me.View is Menu))
         {
-            Application.MouseGrabHandler.GrabMouse (me.View);
+            Application.Mouse.GrabMouse (me.View);
         }
         else
         {
@@ -1645,7 +1645,7 @@ public class MenuBar : View, IDesignable
 
     private MenuBar? GetMouseGrabViewInstance (View? view)
     {
-        if (view is null || Application.MouseGrabHandler.MouseGrabView is null)
+        if (view is null || Application.Mouse.MouseGrabView is null)
         {
             return null;
         }
@@ -1661,7 +1661,7 @@ public class MenuBar : View, IDesignable
             hostView = ((Menu)view).Host;
         }
 
-        View grabView = Application.MouseGrabHandler.MouseGrabView;
+        View grabView = Application.Mouse.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.MouseGrabHandler.MouseGrabView != this)
+                if (Application.Mouse.MouseGrabView != this)
                 {
-                    Application.MouseGrabHandler.GrabMouse (this);
+                    Application.Mouse.GrabMouse (this);
                     _lastLocation = location;
                 }
             }
@@ -333,9 +333,9 @@ public class ScrollSlider : View, IOrientation, IDesignable
             {
                 _lastLocation = -1;
 
-                if (Application.MouseGrabHandler.MouseGrabView == this)
+                if (Application.Mouse.MouseGrabView == this)
                 {
-                    Application.MouseGrabHandler.UngrabMouse ();
+                    Application.Mouse.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.MouseGrabHandler.GrabMouse (this);
+                Application.Mouse.GrabMouse (this);
             }
 
             SetNeedsDraw ();
@@ -1357,7 +1357,7 @@ public class Slider<T> : View, IOrientation
             || mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))
         {
             // End Drag
-            Application.MouseGrabHandler.UngrabMouse ();
+            Application.Mouse.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.MouseGrabHandler.MouseGrabView is null)
+            if (Application.Mouse.MouseGrabView is null)
             {
-                Application.MouseGrabHandler.GrabMouse (this);
+                Application.Mouse.GrabMouse (this);
             }
         }
         else if (ev.Flags == MouseFlags.Button1Released)
         {
             _isButtonReleased = true;
             _isButtonPressed = false;
-            Application.MouseGrabHandler.UngrabMouse ();
+            Application.Mouse.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.MouseGrabHandler.MouseGrabView is { } && Application.MouseGrabHandler.MouseGrabView == this)
+        if (Application.Mouse.MouseGrabView is { } && Application.Mouse.MouseGrabView == this)
         {
-            Application.MouseGrabHandler.UngrabMouse ();
+            Application.Mouse.UngrabMouse ();
         }
 
-        //if (SelectedLength != 0 && !(Application.MouseGrabHandler.MouseGrabView is MenuBar))
+        //if (SelectedLength != 0 && !(Application.Mouse.MouseGrabView is MenuBar))
         //	ClearAllSelection ();
     }
 

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

@@ -1676,15 +1676,15 @@ public class TextView : View, IDesignable
             _lastWasKill = false;
             _columnTrack = CurrentColumn;
 
-            if (Application.MouseGrabHandler.MouseGrabView is null)
+            if (Application.Mouse.MouseGrabView is null)
             {
-                Application.MouseGrabHandler.GrabMouse (this);
+                Application.Mouse.GrabMouse (this);
             }
         }
         else if (ev.Flags.HasFlag (MouseFlags.Button1Released))
         {
             _isButtonReleased = true;
-            Application.MouseGrabHandler.UngrabMouse ();
+            Application.Mouse.UngrabMouse ();
         }
         else if (ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked))
         {
@@ -1886,9 +1886,9 @@ public class TextView : View, IDesignable
     /// <inheritdoc/>
     protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? view)
     {
-        if (Application.MouseGrabHandler.MouseGrabView is { } && Application.MouseGrabHandler.MouseGrabView == this)
+        if (Application.Mouse.MouseGrabView is { } && Application.Mouse.MouseGrabView == this)
         {
-            Application.MouseGrabHandler.UngrabMouse ();
+            Application.Mouse.UngrabMouse ();
         }
     }
 
@@ -2032,7 +2032,7 @@ public class TextView : View, IDesignable
             return null;
         }
 
-        if (Application.MouseGrabHandler.MouseGrabView == this && IsSelecting)
+        if (Application.Mouse.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);

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

@@ -309,7 +309,7 @@ public class ApplicationTests
 
             // Public Properties
             Assert.Null (Application.Top);
-            Assert.Null (Application.MouseGrabHandler.MouseGrabView);
+            Assert.Null (Application.Mouse.MouseGrabView);
 
             // Don't check Application.ForceDriver
             // Assert.Empty (Application.ForceDriver);
@@ -574,7 +574,7 @@ public class ApplicationTests
         Assert.Null (Application.Top);
         RunState rs = Application.Begin (new ());
         Assert.Equal (Application.Top, rs.Toplevel);
-        Assert.Null (Application.MouseGrabHandler.MouseGrabView); // public
+        Assert.Null (Application.Mouse.MouseGrabView); // public
         Application.Top!.Dispose ();
     }
 
@@ -932,7 +932,7 @@ public class ApplicationTests
         Assert.Equal (new (0, 0), w.Frame.Location);
 
         Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
-        Assert.Equal (w.Border, Application.MouseGrabHandler.MouseGrabView);
+        Assert.Equal (w.Border, Application.Mouse.MouseGrabView);
         Assert.Equal (new (0, 0), w.Frame.Location);
 
         // Move down and to the right.

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

+ 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.MouseGrabHandler.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
         Application.ResetState (true);
     }
 }

+ 8 - 8
Tests/UnitTests/View/Mouse/MouseTests.cs

@@ -96,7 +96,7 @@ public class MouseTests : TestsAllViews
 
         view.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -126,7 +126,7 @@ public class MouseTests : TestsAllViews
 
         view.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -156,7 +156,7 @@ public class MouseTests : TestsAllViews
 
         view.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -377,7 +377,7 @@ public class MouseTests : TestsAllViews
 
     //    testView.Dispose ();
 
-    //    // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
+    //    // Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
     //    Application.ResetState (true);
 
     //}
@@ -442,7 +442,7 @@ public class MouseTests : TestsAllViews
 
         testView.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -504,7 +504,7 @@ public class MouseTests : TestsAllViews
 
         testView.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -567,7 +567,7 @@ public class MouseTests : TestsAllViews
 
         testView.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
         Application.ResetState (true);
     }
 
@@ -631,7 +631,7 @@ public class MouseTests : TestsAllViews
 
         testView.Dispose ();
 
-        // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set
+        // Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
         Application.ResetState (true);
     }
     private class MouseEventTestView : View

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

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

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

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

+ 33 - 33
Tests/UnitTestsParallelizable/Application/KeyboardTests.cs

@@ -13,7 +13,7 @@ public class KeyboardTests
     public void Constructor_InitializesKeyBindings ()
     {
         // Arrange & Act
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
 
         // Assert
         Assert.NotNull (keyboard.KeyBindings);
@@ -25,7 +25,7 @@ public class KeyboardTests
     public void QuitKey_DefaultValue_IsEsc ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
 
         // Assert
         Assert.Equal (Key.Esc, keyboard.QuitKey);
@@ -35,7 +35,7 @@ public class KeyboardTests
     public void QuitKey_SetValue_UpdatesKeyBindings ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key newQuitKey = Key.Q.WithCtrl;
 
         // Act
@@ -51,7 +51,7 @@ public class KeyboardTests
     public void ArrangeKey_DefaultValue_IsCtrlF5 ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
 
         // Assert
         Assert.Equal (Key.F5.WithCtrl, keyboard.ArrangeKey);
@@ -61,7 +61,7 @@ public class KeyboardTests
     public void NextTabKey_DefaultValue_IsTab ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
 
         // Assert
         Assert.Equal (Key.Tab, keyboard.NextTabKey);
@@ -71,7 +71,7 @@ public class KeyboardTests
     public void PrevTabKey_DefaultValue_IsShiftTab ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
 
         // Assert
         Assert.Equal (Key.Tab.WithShift, keyboard.PrevTabKey);
@@ -81,7 +81,7 @@ public class KeyboardTests
     public void NextTabGroupKey_DefaultValue_IsF6 ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
 
         // Assert
         Assert.Equal (Key.F6, keyboard.NextTabGroupKey);
@@ -91,7 +91,7 @@ public class KeyboardTests
     public void PrevTabGroupKey_DefaultValue_IsShiftF6 ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
 
         // Assert
         Assert.Equal (Key.F6.WithShift, keyboard.PrevTabGroupKey);
@@ -101,7 +101,7 @@ public class KeyboardTests
     public void KeyBindings_Add_CanAddCustomBinding ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key customKey = Key.K.WithCtrl;
 
         // Act
@@ -116,7 +116,7 @@ public class KeyboardTests
     public void KeyBindings_Remove_CanRemoveBinding ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key customKey = Key.K.WithCtrl;
         keyboard.KeyBindings.Add (customKey, Command.Accept);
 
@@ -131,7 +131,7 @@ public class KeyboardTests
     public void KeyDown_Event_CanBeSubscribed ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         bool eventRaised = false;
 
         // Act
@@ -148,7 +148,7 @@ public class KeyboardTests
     public void KeyUp_Event_CanBeSubscribed ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         bool eventRaised = false;
 
         // Act
@@ -165,7 +165,7 @@ public class KeyboardTests
     public void InvokeCommand_WithInvalidCommand_ThrowsNotSupportedException ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         // Pick a command that isn't registered
         Command invalidCommand = (Command)9999;
         Key testKey = Key.A;
@@ -179,8 +179,8 @@ public class KeyboardTests
     public void Multiple_Keyboards_CanExistIndependently ()
     {
         // Arrange & Act
-        var keyboard1 = new Keyboard ();
-        var keyboard2 = new Keyboard ();
+        var keyboard1 = new KeyboardImpl ();
+        var keyboard2 = new KeyboardImpl ();
 
         keyboard1.QuitKey = Key.Q.WithCtrl;
         keyboard2.QuitKey = Key.X.WithCtrl;
@@ -195,7 +195,7 @@ public class KeyboardTests
     public void KeyBindings_Replace_UpdatesExistingBinding ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key oldKey = Key.Esc;
         Key newKey = Key.Q.WithCtrl;
 
@@ -217,7 +217,7 @@ public class KeyboardTests
     public void KeyBindings_Clear_RemovesAllBindings ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         // Verify initial state has bindings
         Assert.True (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out _));
 
@@ -232,7 +232,7 @@ public class KeyboardTests
     public void AddKeyBindings_PopulatesDefaultBindings ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         keyboard.KeyBindings.Clear ();
         Assert.False (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out _));
 
@@ -250,7 +250,7 @@ public class KeyboardTests
     public void KeyBindings_Add_Adds ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
 
         // Act
         keyboard.KeyBindings.Add (Key.A, Command.Accept);
@@ -267,7 +267,7 @@ public class KeyboardTests
     public void KeyBindings_Remove_Removes ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         keyboard.KeyBindings.Add (Key.A, Command.Accept);
         Assert.True (keyboard.KeyBindings.TryGet (Key.A, out _));
 
@@ -282,7 +282,7 @@ public class KeyboardTests
     public void QuitKey_Default_Is_Esc ()
     {
         // Arrange & Act
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
 
         // Assert
         Assert.Equal (Key.Esc, keyboard.QuitKey);
@@ -292,7 +292,7 @@ public class KeyboardTests
     public void QuitKey_Setter_UpdatesBindings ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key prevKey = keyboard.QuitKey;
 
         // Act - Change QuitKey
@@ -309,7 +309,7 @@ public class KeyboardTests
     public void NextTabKey_Setter_UpdatesBindings ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key prevKey = keyboard.NextTabKey;
         Key newKey = Key.N.WithCtrl;
 
@@ -326,7 +326,7 @@ public class KeyboardTests
     public void PrevTabKey_Setter_UpdatesBindings ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key newKey = Key.P.WithCtrl;
 
         // Act
@@ -342,7 +342,7 @@ public class KeyboardTests
     public void NextTabGroupKey_Setter_UpdatesBindings ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key newKey = Key.PageDown.WithCtrl;
 
         // Act
@@ -359,7 +359,7 @@ public class KeyboardTests
     public void PrevTabGroupKey_Setter_UpdatesBindings ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key newKey = Key.PageUp.WithCtrl;
 
         // Act
@@ -376,7 +376,7 @@ public class KeyboardTests
     public void ArrangeKey_Setter_UpdatesBindings ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key newKey = Key.A.WithCtrl;
 
         // Act
@@ -392,7 +392,7 @@ public class KeyboardTests
     public void KeyBindings_AddWithTarget_StoresTarget ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         var view = new View ();
 
         // Act
@@ -410,7 +410,7 @@ public class KeyboardTests
     public void InvokeCommandsBoundToKey_ReturnsNull_WhenNoBindingExists ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key unboundKey = Key.Z.WithAlt.WithCtrl;
 
         // Act
@@ -424,7 +424,7 @@ public class KeyboardTests
     public void InvokeCommandsBoundToKey_InvokesCommand_WhenBindingExists ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         // QuitKey has a bound command by default
 
         // Act
@@ -440,8 +440,8 @@ public class KeyboardTests
     public void Multiple_Keyboards_Independent_KeyBindings ()
     {
         // Arrange
-        var keyboard1 = new Keyboard ();
-        var keyboard2 = new Keyboard ();
+        var keyboard1 = new KeyboardImpl ();
+        var keyboard2 = new KeyboardImpl ();
 
         // Act
         keyboard1.KeyBindings.Add (Key.X, Command.Accept);
@@ -459,7 +459,7 @@ public class KeyboardTests
     public void KeyBindings_Replace_PreservesCommandsForNewKey ()
     {
         // Arrange
-        var keyboard = new Keyboard ();
+        var keyboard = new KeyboardImpl ();
         Key oldKey = Key.Esc;
         Key newKey = Key.Q.WithCtrl;
 

+ 444 - 0
Tests/UnitTestsParallelizable/Application/MouseInterfaceTests.cs

@@ -0,0 +1,444 @@
+using Terminal.Gui.App;
+using Xunit.Abstractions;
+
+namespace UnitTests_Parallelizable.ApplicationTests;
+
+/// <summary>
+///     Parallelizable tests for IMouse interface.
+///     Tests the decoupled mouse handling without Application.Init or global state.
+/// </summary>
+[Trait ("Category", "Input")]
+public class MouseInterfaceTests (ITestOutputHelper output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    #region IMouse Basic Properties
+
+    [Fact]
+    public void Mouse_LastMousePosition_InitiallyNull ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+
+        // Act & Assert
+        Assert.Null (mouse.LastMousePosition);
+    }
+
+    [Theory]
+    [InlineData (0, 0)]
+    [InlineData (10, 20)]
+    [InlineData (-5, -10)]
+    [InlineData (100, 200)]
+    public void Mouse_LastMousePosition_CanBeSetAndRetrieved (int x, int y)
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        Point testPosition = new (x, y);
+
+        // Act
+        mouse.LastMousePosition = testPosition;
+
+        // Assert
+        Assert.Equal (testPosition, mouse.LastMousePosition);
+        Assert.Equal (testPosition, mouse.GetLastMousePosition ());
+    }
+
+    [Fact]
+    public void Mouse_IsMouseDisabled_DefaultsFalse ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+
+        // Act & Assert
+        Assert.False (mouse.IsMouseDisabled);
+    }
+
+    [Theory]
+    [InlineData (true)]
+    [InlineData (false)]
+    public void Mouse_IsMouseDisabled_CanBeSetAndRetrieved (bool disabled)
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+
+        // Act
+        mouse.IsMouseDisabled = disabled;
+
+        // Assert
+        Assert.Equal (disabled, mouse.IsMouseDisabled);
+    }
+
+    [Fact]
+    public void Mouse_CachedViewsUnderMouse_InitiallyEmpty ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+
+        // Act & Assert
+        Assert.NotNull (mouse.CachedViewsUnderMouse);
+        Assert.Empty (mouse.CachedViewsUnderMouse);
+    }
+
+    #endregion
+
+    #region IMouse Event Handling
+
+    [Fact]
+    public void Mouse_MouseEvent_CanSubscribeAndFire ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        var eventFired = false;
+        MouseEventArgs capturedArgs = null;
+
+        mouse.MouseEvent += (sender, args) =>
+        {
+            eventFired = true;
+            capturedArgs = args;
+        };
+
+        MouseEventArgs testEvent = new ()
+        {
+            ScreenPosition = new Point (5, 10),
+            Flags = MouseFlags.Button1Pressed
+        };
+
+        // Act
+        mouse.RaiseMouseEvent (testEvent);
+
+        // Assert
+        Assert.True (eventFired);
+        Assert.NotNull (capturedArgs);
+        Assert.Equal (testEvent.ScreenPosition, capturedArgs.ScreenPosition);
+        Assert.Equal (testEvent.Flags, capturedArgs.Flags);
+    }
+
+    [Fact]
+    public void Mouse_MouseEvent_CanUnsubscribe ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        var eventCount = 0;
+
+        void Handler (object sender, MouseEventArgs args) => eventCount++;
+
+        mouse.MouseEvent += Handler;
+
+        MouseEventArgs testEvent = new ()
+        {
+            ScreenPosition = new Point (0, 0),
+            Flags = MouseFlags.Button1Pressed
+        };
+
+        // Act - Fire once
+        mouse.RaiseMouseEvent (testEvent);
+        Assert.Equal (1, eventCount);
+
+        // Unsubscribe
+        mouse.MouseEvent -= Handler;
+
+        // Fire again
+        mouse.RaiseMouseEvent (testEvent);
+
+        // Assert - Count should not increase
+        Assert.Equal (1, eventCount);
+    }
+
+    [Fact]
+    public void Mouse_RaiseMouseEvent_WithDisabledMouse_DoesNotFireEvent ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        var eventFired = false;
+
+        mouse.MouseEvent += (sender, args) => { eventFired = true; };
+        mouse.IsMouseDisabled = true;
+
+        MouseEventArgs testEvent = new ()
+        {
+            ScreenPosition = new Point (0, 0),
+            Flags = MouseFlags.Button1Pressed
+        };
+
+        // Act
+        mouse.RaiseMouseEvent (testEvent);
+
+        // Assert
+        Assert.False (eventFired);
+    }
+
+    [Theory]
+    [InlineData (MouseFlags.Button1Pressed)]
+    [InlineData (MouseFlags.Button1Released)]
+    [InlineData (MouseFlags.Button1Clicked)]
+    [InlineData (MouseFlags.Button2Pressed)]
+    [InlineData (MouseFlags.WheeledUp)]
+    [InlineData (MouseFlags.ReportMousePosition)]
+    public void Mouse_RaiseMouseEvent_CorrectlyPassesFlags (MouseFlags flags)
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        MouseFlags? capturedFlags = null;
+
+        mouse.MouseEvent += (sender, args) => { capturedFlags = args.Flags; };
+
+        MouseEventArgs testEvent = new ()
+        {
+            ScreenPosition = new Point (5, 5),
+            Flags = flags
+        };
+
+        // Act
+        mouse.RaiseMouseEvent (testEvent);
+
+        // Assert
+        Assert.NotNull (capturedFlags);
+        Assert.Equal (flags, capturedFlags.Value);
+    }
+
+    #endregion
+
+    #region IMouse ResetState
+
+    [Fact]
+    public void Mouse_ResetState_ClearsCachedViews ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        View testView = new () { Width = 10, Height = 10 };
+
+        mouse.CachedViewsUnderMouse.Add (testView);
+        Assert.Single (mouse.CachedViewsUnderMouse);
+
+        // Act
+        mouse.ResetState ();
+
+        // Assert
+        Assert.Empty (mouse.CachedViewsUnderMouse);
+
+        testView.Dispose ();
+    }
+
+    [Fact]
+    public void Mouse_ResetState_ClearsEventHandlers ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        var eventCount = 0;
+
+        mouse.MouseEvent += (sender, args) => eventCount++;
+
+        MouseEventArgs testEvent = new ()
+        {
+            ScreenPosition = new Point (0, 0),
+            Flags = MouseFlags.Button1Pressed
+        };
+
+        // Verify event fires before reset
+        mouse.RaiseMouseEvent (testEvent);
+        Assert.Equal (1, eventCount);
+
+        // Act
+        mouse.ResetState ();
+
+        // Raise event again
+        mouse.RaiseMouseEvent (testEvent);
+
+        // Assert - Event count should not increase after reset
+        Assert.Equal (1, eventCount);
+    }
+
+    [Fact]
+    public void Mouse_ResetState_DoesNotClearLastMousePosition ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        Point testPosition = new (42, 84);
+
+        mouse.LastMousePosition = testPosition;
+
+        // Act
+        mouse.ResetState ();
+
+        // Assert - LastMousePosition should NOT be cleared (per design)
+        Assert.Equal (testPosition, mouse.LastMousePosition);
+    }
+
+    #endregion
+
+    #region IMouse Isolation
+
+    [Fact]
+    public void Mouse_Instances_AreIndependent ()
+    {
+        // Arrange
+        MouseImpl mouse1 = new ();
+        MouseImpl mouse2 = new ();
+
+        // Act
+        mouse1.IsMouseDisabled = true;
+        mouse1.LastMousePosition = new Point (10, 10);
+
+        // Assert - mouse2 should be unaffected
+        Assert.False (mouse2.IsMouseDisabled);
+        Assert.Null (mouse2.LastMousePosition);
+    }
+
+    [Fact]
+    public void Mouse_Events_AreIndependent ()
+    {
+        // Arrange
+        MouseImpl mouse1 = new ();
+        var mouse1EventCount = 0;
+
+        MouseImpl mouse2 = new ();
+        var mouse2EventCount = 0;
+
+        mouse1.MouseEvent += (sender, args) => mouse1EventCount++;
+        mouse2.MouseEvent += (sender, args) => mouse2EventCount++;
+
+        MouseEventArgs testEvent = new ()
+        {
+            ScreenPosition = new Point (0, 0),
+            Flags = MouseFlags.Button1Pressed
+        };
+
+        // Act
+        mouse1.RaiseMouseEvent (testEvent);
+
+        // Assert
+        Assert.Equal (1, mouse1EventCount);
+        Assert.Equal (0, mouse2EventCount);
+    }
+
+    [Fact]
+    public void Mouse_CachedViews_AreIndependent ()
+    {
+        // Arrange
+        MouseImpl mouse1 = new ();
+        MouseImpl mouse2 = new ();
+
+        View view1 = new ();
+        View view2 = new ();
+
+        // Act
+        mouse1.CachedViewsUnderMouse.Add (view1);
+        mouse2.CachedViewsUnderMouse.Add (view2);
+
+        // Assert
+        Assert.Single (mouse1.CachedViewsUnderMouse);
+        Assert.Single (mouse2.CachedViewsUnderMouse);
+        Assert.Contains (view1, mouse1.CachedViewsUnderMouse);
+        Assert.Contains (view2, mouse2.CachedViewsUnderMouse);
+        Assert.DoesNotContain (view2, mouse1.CachedViewsUnderMouse);
+        Assert.DoesNotContain (view1, mouse2.CachedViewsUnderMouse);
+
+        view1.Dispose ();
+        view2.Dispose ();
+    }
+
+    #endregion
+
+    #region Mouse Grab Tests
+
+    [Fact]
+    public void Mouse_GrabMouse_SetsMouseGrabView ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        View testView = new ();
+
+        // Act
+        mouse.GrabMouse (testView);
+
+        // Assert
+        Assert.Equal (testView, mouse.MouseGrabView);
+    }
+
+    [Fact]
+    public void Mouse_UngrabMouse_ClearsMouseGrabView ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        View testView = new ();
+        mouse.GrabMouse (testView);
+
+        // Act
+        mouse.UngrabMouse ();
+
+        // Assert
+        Assert.Null (mouse.MouseGrabView);
+    }
+
+    [Fact]
+    public void Mouse_GrabbingMouse_CanBeCanceled ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        View testView = new ();
+        var eventFired = false;
+
+        mouse.GrabbingMouse += (sender, args) =>
+        {
+            eventFired = true;
+            args.Cancel = true;
+        };
+
+        // Act
+        mouse.GrabMouse (testView);
+
+        // Assert
+        Assert.True (eventFired);
+        Assert.Null (mouse.MouseGrabView); // Should not be set because it was cancelled
+    }
+
+    [Fact]
+    public void Mouse_GrabbedMouse_EventFired ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        View testView = new ();
+        var eventFired = false;
+        View? eventView = null;
+
+        mouse.GrabbedMouse += (sender, args) =>
+        {
+            eventFired = true;
+            eventView = args.View;
+        };
+
+        // Act
+        mouse.GrabMouse (testView);
+
+        // Assert
+        Assert.True (eventFired);
+        Assert.Equal (testView, eventView);
+    }
+
+    [Fact]
+    public void Mouse_UnGrabbedMouse_EventFired ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        View testView = new ();
+        mouse.GrabMouse (testView);
+
+        var eventFired = false;
+        View? eventView = null;
+
+        mouse.UnGrabbedMouse += (sender, args) =>
+        {
+            eventFired = true;
+            eventView = args.View;
+        };
+
+        // Act
+        mouse.UngrabMouse ();
+
+        // Assert
+        Assert.True (eventFired);
+        Assert.Equal (testView, eventView);
+    }
+
+    #endregion
+}

+ 125 - 0
Tests/UnitTestsParallelizable/Application/MouseTests.cs

@@ -0,0 +1,125 @@
+using Terminal.Gui.App;
+using Xunit.Abstractions;
+
+namespace UnitTests_Parallelizable.ApplicationTests;
+
+/// <summary>
+///     Tests for the <see cref="IMouse"/> interface and <see cref="MouseImpl"/> implementation.
+///     These tests demonstrate the decoupled mouse handling that enables parallel test execution.
+/// </summary>
+public class MouseTests (ITestOutputHelper output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    [Fact]
+    public void Mouse_Instance_CreatedSuccessfully ()
+    {
+        // Arrange & Act
+        MouseImpl mouse = new ();
+
+        // Assert
+        Assert.NotNull (mouse);
+        Assert.False (mouse.IsMouseDisabled);
+        Assert.Null (mouse.LastMousePosition);
+    }
+
+    [Fact]
+    public void Mouse_LastMousePosition_CanBeSetAndRetrieved ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        Point expectedPosition = new (10, 20);
+
+        // Act
+        mouse.LastMousePosition = expectedPosition;
+        Point? actualPosition = mouse.GetLastMousePosition ();
+
+        // Assert
+        Assert.Equal (expectedPosition, actualPosition);
+    }
+
+    [Fact]
+    public void Mouse_IsMouseDisabled_CanBeSetAndRetrieved ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+
+        // Act
+        mouse.IsMouseDisabled = true;
+
+        // Assert
+        Assert.True (mouse.IsMouseDisabled);
+    }
+
+    [Fact]
+    public void Mouse_CachedViewsUnderMouse_InitializedEmpty ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+
+        // Assert
+        Assert.NotNull (mouse.CachedViewsUnderMouse);
+        Assert.Empty (mouse.CachedViewsUnderMouse);
+    }
+
+    [Fact]
+    public void Mouse_ResetState_ClearsEventAndCachedViews ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        var eventFired = false;
+        mouse.MouseEvent += (sender, args) => eventFired = true;
+        mouse.CachedViewsUnderMouse.Add (new View ());
+
+        // Act
+        mouse.ResetState ();
+
+        // Assert - CachedViewsUnderMouse should be cleared
+        Assert.Empty (mouse.CachedViewsUnderMouse);
+        
+        // Event handlers should be cleared
+        MouseEventArgs mouseEvent = new () { ScreenPosition = new Point (0, 0), Flags = MouseFlags.Button1Pressed };
+        mouse.RaiseMouseEvent (mouseEvent);
+        Assert.False (eventFired, "Event should not fire after ResetState");
+    }
+
+    [Fact]
+    public void Mouse_RaiseMouseEvent_DoesNotUpdateLastPositionWhenNotInitialized ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        MouseEventArgs mouseEvent = new () { ScreenPosition = new Point (5, 10), Flags = MouseFlags.Button1Pressed };
+
+        // Act - Application is not initialized, so LastMousePosition should not be set
+        mouse.RaiseMouseEvent (mouseEvent);
+
+        // Assert
+        // Since Application.Initialized is false, LastMousePosition should remain null
+        // This behavior matches the original implementation
+        Assert.Null (mouse.LastMousePosition);
+    }
+
+    [Fact]
+    public void Mouse_MouseEvent_CanBeSubscribedAndUnsubscribed ()
+    {
+        // Arrange
+        MouseImpl mouse = new ();
+        var eventCount = 0;
+        EventHandler<MouseEventArgs> handler = (sender, args) => eventCount++;
+
+        // Act - Subscribe
+        mouse.MouseEvent += handler;
+        MouseEventArgs mouseEvent = new () { ScreenPosition = new Point (0, 0), Flags = MouseFlags.Button1Pressed };
+        mouse.RaiseMouseEvent (mouseEvent);
+
+        // Assert - Event fired once
+        Assert.Equal (1, eventCount);
+
+        // Act - Unsubscribe
+        mouse.MouseEvent -= handler;
+        mouse.RaiseMouseEvent (mouseEvent);
+
+        // Assert - Event count unchanged
+        Assert.Equal (1, eventCount);
+    }
+}

+ 1 - 1
Tests/UnitTestsParallelizable/TestSetup.cs

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

+ 498 - 0
Tests/UnitTestsParallelizable/View/Mouse/MouseEventRoutingTests.cs

@@ -0,0 +1,498 @@
+using Terminal.Gui.App;
+using Xunit.Abstractions;
+
+namespace UnitTests_Parallelizable.ApplicationTests;
+
+/// <summary>
+///     Parallelizable tests for mouse event routing and coordinate transformation.
+///     These tests validate mouse event handling without Application.Begin or global state.
+/// </summary>
+[Trait ("Category", "Input")]
+public class MouseEventRoutingTests (ITestOutputHelper output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    #region Mouse Event Routing to Views
+
+    [Theory]
+    [InlineData (5, 5, 5, 5, true)]   // Click inside view
+    [InlineData (0, 0, 0, 0, true)]   // Click at origin
+    [InlineData (9, 9, 9, 9, true)]   // Click at far corner (view is 10x10)
+    [InlineData (10, 10, -1, -1, false)] // Click outside view
+    [InlineData (-1, -1, -1, -1, false)] // Click outside view
+    public void View_NewMouseEvent_ReceivesCorrectCoordinates (int screenX, int screenY, int expectedViewX, int expectedViewY, bool shouldReceive)
+    {
+        // Arrange
+        View view = new ()
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = 10
+        };
+
+        Point? receivedPosition = null;
+        var eventReceived = false;
+
+        view.MouseEvent += (sender, args) =>
+        {
+            eventReceived = true;
+            receivedPosition = args.Position;
+        };
+
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (screenX, screenY),
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        // Act
+        view.NewMouseEvent (mouseEvent);
+
+        // Assert
+        if (shouldReceive)
+        {
+            Assert.True (eventReceived);
+            Assert.NotNull (receivedPosition);
+            Assert.Equal (expectedViewX, receivedPosition.Value.X);
+            Assert.Equal (expectedViewY, receivedPosition.Value.Y);
+        }
+
+        view.Dispose ();
+    }
+
+    [Theory]
+    [InlineData (0, 0, 5, 5, 5, 5, true)]   // View at origin, click at (5,5) in view
+    [InlineData (10, 10, 5, 5, 5, 5, true)] // View offset, but we still pass view-relative coords
+    [InlineData (0, 0, 0, 0, 0, 0, true)]   // View at origin, click at origin
+    [InlineData (5, 5, 9, 9, 9, 9, true)]   // View offset, click at far corner (view-relative)
+    [InlineData (0, 0, 10, 10, -1, -1, false)] // Click outside view bounds
+    [InlineData (0, 0, -1, -1, -1, -1, false)] // Click outside view bounds
+    public void View_WithOffset_ReceivesCorrectCoordinates (
+        int viewX,
+        int viewY,
+        int viewRelativeX,
+        int viewRelativeY,
+        int expectedViewX,
+        int expectedViewY,
+        bool shouldReceive)
+    {
+        // Arrange
+        // Note: When testing View.NewMouseEvent directly (without Application routing),
+        // coordinates are already view-relative. The view's X/Y position doesn't affect
+        // the coordinate transformation at this level.
+        View view = new ()
+        {
+            X = viewX,
+            Y = viewY,
+            Width = 10,
+            Height = 10
+        };
+
+        Point? receivedPosition = null;
+        var eventReceived = false;
+
+        view.MouseEvent += (sender, args) =>
+        {
+            eventReceived = true;
+            receivedPosition = args.Position;
+        };
+
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (viewRelativeX, viewRelativeY),
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        // Act
+        view.NewMouseEvent (mouseEvent);
+
+        // Assert
+        if (shouldReceive)
+        {
+            Assert.True (eventReceived, $"Event should be received at view-relative ({viewRelativeX},{viewRelativeY})");
+            Assert.NotNull (receivedPosition);
+            Assert.Equal (expectedViewX, receivedPosition.Value.X);
+            Assert.Equal (expectedViewY, receivedPosition.Value.Y);
+        }
+
+        view.Dispose ();
+    }
+
+    #endregion
+
+    #region View Hierarchy Mouse Event Routing
+
+    [Fact]
+    public void SubView_ReceivesMouseEvent_WithCorrectRelativeCoordinates ()
+    {
+        // Arrange
+        View superView = new ()
+        {
+            X = 0,
+            Y = 0,
+            Width = 20,
+            Height = 20
+        };
+
+        View subView = new ()
+        {
+            X = 5,
+            Y = 5,
+            Width = 10,
+            Height = 10
+        };
+
+        superView.Add (subView);
+
+        Point? subViewReceivedPosition = null;
+        var subViewEventReceived = false;
+
+        subView.MouseEvent += (sender, args) =>
+        {
+            subViewEventReceived = true;
+            subViewReceivedPosition = args.Position;
+        };
+
+        // Click at position (2, 2) relative to subView (which is at 5,5 relative to superView)
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (2, 2), // Relative to subView
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        // Act
+        subView.NewMouseEvent (mouseEvent);
+
+        // Assert
+        Assert.True (subViewEventReceived);
+        Assert.NotNull (subViewReceivedPosition);
+        Assert.Equal (2, subViewReceivedPosition.Value.X);
+        Assert.Equal (2, subViewReceivedPosition.Value.Y);
+
+        subView.Dispose ();
+        superView.Dispose ();
+    }
+
+    [Fact]
+    public void MouseClick_OnSubView_RaisesMouseClickEvent ()
+    {
+        // Arrange
+        View superView = new ()
+        {
+            Width = 20,
+            Height = 20
+        };
+
+        View subView = new ()
+        {
+            X = 5,
+            Y = 5,
+            Width = 10,
+            Height = 10
+        };
+
+        superView.Add (subView);
+
+        var clickCount = 0;
+        subView.MouseClick += (sender, args) => clickCount++;
+
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (5, 5),
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        // Act
+        subView.NewMouseEvent (mouseEvent);
+
+        // Assert
+        Assert.Equal (1, clickCount);
+
+        subView.Dispose ();
+        superView.Dispose ();
+    }
+
+    #endregion
+
+    #region Mouse Event Propagation
+
+    [Fact]
+    public void View_HandledEvent_StopsPropagation ()
+    {
+        // Arrange
+        View view = new () { Width = 10, Height = 10 };
+        var handlerCalled = false;
+        var clickHandlerCalled = false;
+
+        view.MouseEvent += (sender, args) =>
+        {
+            handlerCalled = true;
+            args.Handled = true; // Mark as handled
+        };
+
+        view.MouseClick += (sender, args) => { clickHandlerCalled = true; };
+
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (5, 5),
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        // Act
+        bool? result = view.NewMouseEvent (mouseEvent);
+
+        // Assert
+        Assert.True (result.HasValue && result.Value); // Event was handled
+        Assert.True (handlerCalled);
+        Assert.False (clickHandlerCalled); // Click handler should not be called when event is handled
+
+        view.Dispose ();
+    }
+
+    [Fact]
+    public void View_UnhandledEvent_ContinuesProcessing ()
+    {
+        // Arrange
+        View view = new () { Width = 10, Height = 10 };
+        var eventHandlerCalled = false;
+        var clickHandlerCalled = false;
+
+        view.MouseEvent += (sender, args) =>
+        {
+            eventHandlerCalled = true;
+            // Don't set Handled = true
+        };
+
+        view.MouseClick += (sender, args) => { clickHandlerCalled = true; };
+
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (5, 5),
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        // Act
+        view.NewMouseEvent (mouseEvent);
+
+        // Assert
+        Assert.True (eventHandlerCalled);
+        Assert.True (clickHandlerCalled); // Click handler should be called when event is not handled
+
+        view.Dispose ();
+    }
+
+    #endregion
+
+    #region Mouse Button Events
+
+    [Theory]
+    [InlineData (MouseFlags.Button1Pressed, 1, 0, 0)]
+    [InlineData (MouseFlags.Button1Released, 0, 1, 0)]
+    [InlineData (MouseFlags.Button1Clicked, 0, 0, 1)]
+    public void View_MouseButtonEvents_RaiseCorrectHandlers (MouseFlags flags, int expectedPressed, int expectedReleased, int expectedClicked)
+    {
+        // Arrange
+        View view = new () { Width = 10, Height = 10 };
+        var pressedCount = 0;
+        var releasedCount = 0;
+        var clickedCount = 0;
+
+        view.MouseEvent += (sender, args) =>
+        {
+            if (args.Flags.HasFlag (MouseFlags.Button1Pressed))
+            {
+                pressedCount++;
+            }
+
+            if (args.Flags.HasFlag (MouseFlags.Button1Released))
+            {
+                releasedCount++;
+            }
+        };
+
+        view.MouseClick += (sender, args) => { clickedCount++; };
+
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (5, 5),
+            Flags = flags
+        };
+
+        // Act
+        view.NewMouseEvent (mouseEvent);
+
+        // Assert
+        Assert.Equal (expectedPressed, pressedCount);
+        Assert.Equal (expectedReleased, releasedCount);
+        Assert.Equal (expectedClicked, clickedCount);
+
+        view.Dispose ();
+    }
+
+    [Theory]
+    [InlineData (MouseFlags.Button1Clicked)]
+    [InlineData (MouseFlags.Button2Clicked)]
+    [InlineData (MouseFlags.Button3Clicked)]
+    [InlineData (MouseFlags.Button4Clicked)]
+    public void View_AllMouseButtons_TriggerClickEvent (MouseFlags clickFlag)
+    {
+        // Arrange
+        View view = new () { Width = 10, Height = 10 };
+        var clickCount = 0;
+
+        view.MouseClick += (sender, args) => clickCount++;
+
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (5, 5),
+            Flags = clickFlag
+        };
+
+        // Act
+        view.NewMouseEvent (mouseEvent);
+
+        // Assert
+        Assert.Equal (1, clickCount);
+
+        view.Dispose ();
+    }
+
+    #endregion
+
+    #region Disabled View Tests
+
+    [Fact]
+    public void View_Disabled_DoesNotRaiseMouseEvent ()
+    {
+        // Arrange
+        View view = new ()
+        {
+            Width = 10,
+            Height = 10,
+            Enabled = false
+        };
+
+        var eventCalled = false;
+        view.MouseEvent += (sender, args) => { eventCalled = true; };
+
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (5, 5),
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        // Act
+        view.NewMouseEvent (mouseEvent);
+
+        // Assert
+        Assert.False (eventCalled);
+
+        view.Dispose ();
+    }
+
+    [Fact]
+    public void View_Disabled_DoesNotRaiseMouseClickEvent ()
+    {
+        // Arrange
+        View view = new ()
+        {
+            Width = 10,
+            Height = 10,
+            Enabled = false
+        };
+
+        var clickCalled = false;
+        view.MouseClick += (sender, args) => { clickCalled = true; };
+
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (5, 5),
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        // Act
+        view.NewMouseEvent (mouseEvent);
+
+        // Assert
+        Assert.False (clickCalled);
+
+        view.Dispose ();
+    }
+
+    #endregion
+
+    #region Focus and Selection Tests
+
+    [Theory]
+    [InlineData (true, true)]
+    [InlineData (false, false)]
+    public void MouseClick_SetsFocus_BasedOnCanFocus (bool canFocus, bool expectFocus)
+    {
+        // Arrange
+        View superView = new () { CanFocus = true, Width = 20, Height = 20 };
+        View subView = new ()
+        {
+            X = 5,
+            Y = 5,
+            Width = 10,
+            Height = 10,
+            CanFocus = canFocus
+        };
+
+        superView.Add (subView);
+        superView.SetFocus (); // Give superView focus first
+
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (2, 2),
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        // Act
+        subView.NewMouseEvent (mouseEvent);
+
+        // Assert
+        Assert.Equal (expectFocus, subView.HasFocus);
+
+        subView.Dispose ();
+        superView.Dispose ();
+    }
+
+    [Fact]
+    public void MouseClick_RaisesSelecting_WhenCanFocus ()
+    {
+        // Arrange
+        View superView = new () { CanFocus = true, Width = 20, Height = 20 };
+        View view = new ()
+        {
+            X = 5,
+            Y = 5,
+            Width = 10,
+            Height = 10,
+            CanFocus = true
+        };
+
+        superView.Add (view);
+
+        var selectingCount = 0;
+        view.Selecting += (sender, args) => selectingCount++;
+
+        MouseEventArgs mouseEvent = new ()
+        {
+            Position = new Point (5, 5),
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        // Act
+        view.NewMouseEvent (mouseEvent);
+
+        // Assert
+        Assert.Equal (1, selectingCount);
+
+        view.Dispose ();
+        superView.Dispose ();
+    }
+
+    #endregion
+}

BIN=BIN
local_packages/Terminal.Gui.2.0.0.nupkg


BIN=BIN
local_packages/Terminal.Gui.2.0.0.snupkg