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 <585482+tig@users.noreply.github.com>

* Add enhanced documentation for Application.Mouse property

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add parallelizable unit tests for IMouse interface

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Refactor Application.Mouse for decoupling and parallelism

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Move HandleMouseGrab method to IMouseGrabHandler interface

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add parallelizable tests for IMouse and IMouseGrabHandler interfaces

Co-authored-by: tig <585482+tig@users.noreply.github.com>

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

Co-authored-by: tig <585482+tig@users.noreply.github.com>

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

Co-authored-by: tig <585482+tig@users.noreply.github.com>

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

Co-authored-by: tig <585482+tig@users.noreply.github.com>

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

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* 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 <585482+tig@users.noreply.github.com>

* Rename Mouse to MouseImpl and Keyboard to KeyboardImpl for consistency

Co-authored-by: tig <585482+tig@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tig <585482+tig@users.noreply.github.com>
Co-authored-by: Tig <tig@users.noreply.github.com>
This commit is contained in:
Copilot
2025-10-25 08:48:26 -06:00
committed by GitHub
parent db5fdebfa9
commit 4974343e74
43 changed files with 2263 additions and 766 deletions

View File

@@ -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.

View File

@@ -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 ();
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);