Files
Terminal.Gui/Tests/TerminalGuiFluentTesting/GuiTestContext.Input.cs
Tig f548059a27 Fixes #4258 - Glyphs drawn at mid-point of wide glyphs don't get drawn with clipping (#4462)
* Enhanced `View.Drawing.cs` with improved comments, a new
`DoDrawComplete` method for clip region updates, and
clarified terminology. Added detailed remarks for the
`OnDrawComplete` method and `DrawComplete` event.

Refactored `ViewDrawingClippingTests` to simplify driver
setup, use target-typed `new`, and add a new test for wide
glyph clipping with bordered subviews. Improved handling of
edge cases like empty viewports and nested clips.

Added `WideGlyphs.DrawFlow.md` and
`ViewDrawingClippingTests.DrawFlow.md` to document the draw
flow, clipping behavior, and coordinate systems for both the
scenario and the test.

Commented out redundant `Driver.Clip` initialization in
`ApplicationImpl`. Added a `BUGBUG` comment in `Border` to
highlight missing redraw logic for `LineStyle` changes.

* Uncomment Driver.Clip initialization in Screen redraw

* Fixed it!

* Fixes #4258 - Correct wide glyph and border rendering

Refactored `OutputBufferImpl.AddStr` to improve handling of wide glyphs:
- Wide glyphs now modify only the first column they occupy, leaving the second column untouched.
- Removed redundant code that set replacement characters and marked cells as not dirty.
- Synchronized cursor updates (`Col` and `Row`) with the buffer lock to prevent race conditions.
- Modularized logic with helper methods for better readability and maintainability.

Updated `WideGlyphs.cs`:
- Removed dashed `BorderStyle` and added border thickness and subview for `arrangeableViewAtEven`.
- Removed unused `superView` initialization.

Enhanced tests:
- Added unit tests to verify correct rendering of borders and content at odd columns overlapping wide glyphs.
- Updated existing tests to reflect the new behavior of wide glyph handling.
- Introduced `DriverAssert.AssertDriverOutputIs` to validate raw ANSI output.

Improved documentation:
- Expanded problem description and root cause analysis in `WideGlyphBorderBugFix.md`.
- Detailed the fix and its impact, ensuring proper layering of content at any column position.

General cleanup:
- Removed unused imports and redundant code.
- Improved code readability and maintainability.

* Code cleanup

* Update Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Terminal.Gui/Drivers/OutputBufferImpl.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fixed test slowness problem

* Simplified

* Rmoved temp .md files

* Refactor I/O handling and improve testability

Refactored `InputProcessor` and `Output` access by replacing direct property usage with `GetInputProcessor()` and `GetOutput()` methods to enhance encapsulation. Introduced `GetLastOutput()` and `GetLastBuffer()` methods for better debugging and testability.

Centralized `StringBuilder` usage in `OutputBase` implementations to ensure consistency. Improved exception handling with clearer messages. Updated tests to align with the refactored structure and added a new test for wide glyph handling.

Enhanced ANSI sequence handling and simplified cursor visibility logic to prevent flickering. Standardized method naming for consistency. Cleaned up redundant code and improved documentation for better developer clarity.

* Refactored `NetOutput`, `FakeOutput`, `UnixOutput`, and `WindowsOutput` classes to support access to `Output` and added a `IDriver.GetOutput` to acess the `IOutput`. `IOutput` now has a `GetLastOutput` method.

Simplified `DriverAssert` logic and enhanced `DriverTests` with a new test for wide glyph clipping across drivers.

Performed general cleanup, including removal of unused code, improved formatting, and adoption of modern C# practices. Added `using System.Diagnostics` in `OutputBufferImpl` for debugging.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-08 12:28:32 -07:00

219 lines
8.2 KiB
C#

using System.Drawing;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
namespace TerminalGuiFluentTesting;
public partial class GuiTestContext
{
/// <summary>
/// Simulates a right click at the given screen coordinates on the current driver.
/// This is a raw input event that goes through entire processing pipeline as though
/// user had pressed the mouse button physically.
/// </summary>
/// <param name="screenX">0 indexed screen coordinates</param>
/// <param name="screenY">0 indexed screen coordinates</param>
/// <returns></returns>
public GuiTestContext RightClick (int screenX, int screenY)
{
return EnqueueMouseEvent (new ()
{
Flags = MouseFlags.Button3Clicked,
ScreenPosition = new (screenX, screenY)
});
}
/// <summary>
/// Simulates a left click at the given screen coordinates on the current driver.
/// This is a raw input event that goes through entire processing pipeline as though
/// user had pressed the mouse button physically.
/// </summary>
/// <param name="screenX">0 indexed screen coordinates</param>
/// <param name="screenY">0 indexed screen coordinates</param>
/// <returns></returns>
public GuiTestContext LeftClick (int screenX, int screenY)
{
return EnqueueMouseEvent (new ()
{
Flags = MouseFlags.Button1Clicked,
ScreenPosition = new (screenX, screenY),
});
}
/// <summary>
/// Simulates a left mouse click on the top-left cell of the Viewport of the View of type TView determined by the
/// <paramref name="evaluator"/>.
/// </summary>
/// <typeparam name="TView"></typeparam>
/// <param name="evaluator"></param>
/// <returns></returns>
public GuiTestContext LeftClick<TView> (Func<TView, bool> evaluator) where TView : View
{
return EnqueueMouseEvent (new ()
{
Flags = MouseFlags.Button1Clicked,
}, evaluator);
}
private GuiTestContext EnqueueMouseEvent (MouseEventArgs mouseEvent)
{
// Enqueue the mouse event
WaitIteration ((app) =>
{
if (app.Driver is { })
{
mouseEvent.Position = mouseEvent.ScreenPosition;
app.Driver.GetInputProcessor ().EnqueueMouseEvent (app, mouseEvent);
}
else
{
Fail ("Expected Application.Driver to be non-null.");
}
});
// Wait for the event to be processed (similar to EnqueueKeyEvent)
return WaitIteration ();
}
private GuiTestContext EnqueueMouseEvent<TView> (MouseEventArgs mouseEvent, Func<TView, bool> evaluator) where TView : View
{
var screen = Point.Empty;
GuiTestContext ctx = WaitIteration ((_) =>
{
TView v = Find (evaluator);
screen = v.ViewportToScreen (new Point (0, 0));
});
mouseEvent.ScreenPosition = screen;
mouseEvent.Position = new Point (0, 0);
EnqueueMouseEvent (mouseEvent);
return ctx;
}
//private GuiTestContext Click (WindowsConsole.ButtonState btn, int screenX, int screenY)
//{
// switch (_driverType)
// {
// case TestDriver.Windows:
// _winInput!.InputQueue!.Enqueue (
// new ()
// {
// EventType = WindowsConsole.EventType.Mouse,
// MouseEvent = new ()
// {
// ButtonState = btn,
// MousePosition = new ((short)screenX, (short)screenY)
// }
// });
// _winInput.InputQueue.Enqueue (
// new ()
// {
// EventType = WindowsConsole.EventType.Mouse,
// MouseEvent = new ()
// {
// ButtonState = WindowsConsole.ButtonState.NoButtonPressed,
// MousePosition = new ((short)screenX, (short)screenY)
// }
// });
// return WaitUntil (() => _winInput.InputQueue.IsEmpty);
// case TestDriver.DotNet:
// int netButton = btn switch
// {
// WindowsConsole.ButtonState.Button1Pressed => 0,
// WindowsConsole.ButtonState.Button2Pressed => 1,
// WindowsConsole.ButtonState.Button3Pressed => 2,
// WindowsConsole.ButtonState.RightmostButtonPressed => 2,
// _ => throw new ArgumentOutOfRangeException (nameof (btn))
// };
// foreach (ConsoleKeyInfo k in NetSequences.Click (netButton, screenX, screenY))
// {
// SendNetKey (k, false);
// }
// return WaitIteration ();
// case TestDriver.Unix:
// int unixButton = btn switch
// {
// WindowsConsole.ButtonState.Button1Pressed => 0,
// WindowsConsole.ButtonState.Button2Pressed => 1,
// WindowsConsole.ButtonState.Button3Pressed => 2,
// WindowsConsole.ButtonState.RightmostButtonPressed => 2,
// _ => throw new ArgumentOutOfRangeException (nameof (btn))
// };
// foreach (ConsoleKeyInfo k in NetSequences.Click (unixButton, screenX, screenY))
// {
// SendUnixKey (k.KeyChar, false);
// }
// return WaitIteration ();
// case TestDriver.Fake:
// int fakeButton = btn switch
// {
// WindowsConsole.ButtonState.Button1Pressed => 0,
// WindowsConsole.ButtonState.Button2Pressed => 1,
// WindowsConsole.ButtonState.Button3Pressed => 2,
// WindowsConsole.ButtonState.RightmostButtonPressed => 2,
// _ => throw new ArgumentOutOfRangeException (nameof (btn))
// };
// foreach (ConsoleKeyInfo k in NetSequences.Click (fakeButton, screenX, screenY))
// {
// SendFakeKey (k, false);
// }
// return WaitIteration ();
// default:
// throw new ArgumentOutOfRangeException ();
// }
//}
/// <summary>
/// Enqueues a key down event to the current driver's input processor.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
/// <summary>
/// Enqueues a key down event to the current driver's input processor.
/// </summary>
public GuiTestContext EnqueueKeyEvent (Key key)
{
//Logging.Trace ($"Enqueuing key: {key}");
// Enqueue the key event and wait for it to be processed.
// We do this by subscribing to the Driver.KeyDown event and waiting until it is raised.
// This prevents the application from missing the key event if we enqueue it and immediately return.
bool keyReceived = false;
if (App?.Driver is { })
{
App.Driver.KeyDown += DriverOnKeyDown;
App.Driver.EnqueueKeyEvent (key);
WaitUntil (() => keyReceived);
}
return this;
void DriverOnKeyDown (object? sender, Key e)
{
App.Driver.KeyDown -= DriverOnKeyDown;
keyReceived = true;
}
}
}