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>
This commit is contained in:
Tig
2025-12-08 12:28:32 -07:00
committed by GitHub
parent 5da7e59aa2
commit f548059a27
20 changed files with 1046 additions and 324 deletions

View File

@@ -158,7 +158,7 @@ public class Keys : Scenario
appKeyListView.SchemeName = "Runnable";
win.Add (onSwallowedListView);
Application.Driver!.InputProcessor.AnsiSequenceSwallowed += (s, e) => { swallowedList.Add (e.Replace ("\x1b", "Esc")); };
Application.Driver!.GetInputProcessor ().AnsiSequenceSwallowed += (s, e) => { swallowedList.Add (e.Replace ("\x1b", "Esc")); };
Application.KeyDown += (s, a) => KeyDownPressUp (a, "Down");
Application.KeyUp += (s, a) => KeyDownPressUp (a, "Up");

View File

@@ -25,7 +25,7 @@ public sealed class WideGlyphs : Scenario
};
// Build the array of codepoints once when subviews are laid out
appWindow.SubViewsLaidOut += (s, e) =>
appWindow.SubViewsLaidOut += (s, _) =>
{
View? view = s as View;
if (view is null)
@@ -34,8 +34,8 @@ public sealed class WideGlyphs : Scenario
}
// Only rebuild if size changed or array is null
if (_codepoints is null ||
_codepoints.GetLength (0) != view.Viewport.Height ||
if (_codepoints is null ||
_codepoints.GetLength (0) != view.Viewport.Height ||
_codepoints.GetLength (1) != view.Viewport.Width)
{
_codepoints = new Rune [view.Viewport.Height, view.Viewport.Width];
@@ -51,7 +51,9 @@ public sealed class WideGlyphs : Scenario
};
// Fill the window with the pre-built codepoints array
appWindow.DrawingContent += (s, e) =>
// For detailed documentation on the draw code flow from Application.Run to this event,
// see WideGlyphs.DrawFlow.md in this directory
appWindow.DrawingContent += (s, _) =>
{
View? view = s as View;
if (view is null || _codepoints is null)
@@ -73,7 +75,7 @@ public sealed class WideGlyphs : Scenario
}
};
Line verticalLineAtEven = new Line ()
Line verticalLineAtEven = new ()
{
X = 10,
Orientation = Orientation.Vertical,
@@ -81,7 +83,7 @@ public sealed class WideGlyphs : Scenario
};
appWindow.Add (verticalLineAtEven);
Line verticalLineAtOdd = new Line ()
Line verticalLineAtOdd = new ()
{
X = 25,
Orientation = Orientation.Vertical,
@@ -97,8 +99,12 @@ public sealed class WideGlyphs : Scenario
Y = 5,
Width = 15,
Height = 5,
BorderStyle = LineStyle.Dashed,
//BorderStyle = LineStyle.Dashed,
};
// Proves it's not LineCanvas related
arrangeableViewAtEven!.Border!.Thickness = new (1);
arrangeableViewAtEven.Border.Add(new View () { Height = Dim.Auto(), Width = Dim.Auto(), Text = "Even" });
appWindow.Add (arrangeableViewAtEven);
View arrangeableViewAtOdd = new ()
@@ -112,6 +118,70 @@ public sealed class WideGlyphs : Scenario
BorderStyle = LineStyle.Dashed,
};
appWindow.Add (arrangeableViewAtOdd);
var superView = new View
{
CanFocus = true,
X = 30, // on an even column to start
Y = Pos.Center (),
Width = Dim.Auto () + 4,
Height = Dim.Auto () + 1,
BorderStyle = LineStyle.Single,
Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable
};
Rune codepoint = Glyphs.Apple;
superView.DrawingContent += (s, e) =>
{
var view = s as View;
for (var r = 0; r < view!.Viewport.Height; r++)
{
for (var c = 0; c < view.Viewport.Width; c += 2)
{
if (codepoint != default (Rune))
{
view.AddRune (c, r, codepoint);
}
}
}
e.DrawContext?.AddDrawnRectangle (view.Viewport);
e.Cancel = true;
};
appWindow.Add (superView);
var viewWithBorderAtX0 = new View
{
Text = "viewWithBorderAtX0",
BorderStyle = LineStyle.Dashed,
X = 0,
Y = 1,
Width = Dim.Auto (),
Height = 3
};
var viewWithBorderAtX1 = new View
{
Text = "viewWithBorderAtX1",
BorderStyle = LineStyle.Dashed,
X = 1,
Y = Pos.Bottom (viewWithBorderAtX0) + 1,
Width = Dim.Auto (),
Height = 3
};
var viewWithBorderAtX2 = new View
{
Text = "viewWithBorderAtX2",
BorderStyle = LineStyle.Dashed,
X = 2,
Y = Pos.Bottom (viewWithBorderAtX1) + 1,
Width = Dim.Auto (),
Height = 3
};
superView.Add (viewWithBorderAtX0, viewWithBorderAtX1, viewWithBorderAtX2);
// Run - Start the application.
Application.Run (appWindow);
appWindow.Dispose ();
@@ -124,6 +194,6 @@ public sealed class WideGlyphs : Scenario
{
Random random = new ();
int codepoint = random.Next (0x4E00, 0x9FFF);
return new Rune (codepoint);
return new (codepoint);
}
}