Selaa lähdekoodia

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 <[email protected]>

* Update Terminal.Gui/Drivers/OutputBufferImpl.cs

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

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

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

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

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

* 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 <[email protected]>
Tig 5 päivää sitten
vanhempi
sitoutus
f548059a27

+ 1 - 1
Examples/UICatalog/Scenarios/Keys.cs

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

+ 78 - 8
Examples/UICatalog/Scenarios/WideGlyphs.cs

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

+ 2 - 1
Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs

@@ -109,6 +109,7 @@ public class NetOutput : OutputBase, IOutput
     /// <inheritdoc />
     protected override void Write (StringBuilder output)
     {
+        base.Write (output);
         try
         {
             Console.Out.Write (output);
@@ -140,7 +141,7 @@ public class NetOutput : OutputBase, IOutput
             }
             catch (Exception)
             {
-                return false;
+                return true;
             }
         }
 

+ 21 - 14
Terminal.Gui/Drivers/DriverImpl.cs

@@ -45,19 +45,19 @@ internal class DriverImpl : IDriver
         ISizeMonitor sizeMonitor
     )
     {
-        InputProcessor = inputProcessor;
+        _inputProcessor = inputProcessor;
         _output = output;
         OutputBuffer = outputBuffer;
         _ansiRequestScheduler = ansiRequestScheduler;
 
-        InputProcessor.KeyDown += (s, e) => KeyDown?.Invoke (s, e);
-        InputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
+        GetInputProcessor ().KeyDown += (s, e) => KeyDown?.Invoke (s, e);
+        GetInputProcessor ().KeyUp += (s, e) => KeyUp?.Invoke (s, e);
 
-        InputProcessor.MouseEvent += (s, e) =>
-                                     {
-                                         //Logging.Logger.LogTrace ($"Mouse {e.Flags} at x={e.ScreenPosition.X} y={e.ScreenPosition.Y}");
-                                         MouseEvent?.Invoke (s, e);
-                                     };
+        GetInputProcessor ().MouseEvent += (s, e) =>
+                                           {
+                                               //Logging.Logger.LogTrace ($"Mouse {e.Flags} at x={e.ScreenPosition.X} y={e.ScreenPosition.Y}");
+                                               MouseEvent?.Invoke (s, e);
+                                           };
 
         SizeMonitor = sizeMonitor;
         SizeMonitor.SizeChanged += OnSizeMonitorOnSizeChanged;
@@ -73,15 +73,18 @@ internal class DriverImpl : IDriver
     public void Init () { throw new NotSupportedException (); }
 
     /// <inheritdoc/>
-    public void Refresh () { _output.Write (OutputBuffer); }
+    public void Refresh ()
+    {
+        _output.Write (OutputBuffer);
+    }
 
     /// <inheritdoc/>
-    public string? GetName () => InputProcessor.DriverName?.ToLowerInvariant ();
+    public string? GetName () => GetInputProcessor ().DriverName?.ToLowerInvariant ();
 
     /// <inheritdoc/>
     public virtual string GetVersionInfo ()
     {
-        string type = InputProcessor.DriverName ?? throw new ArgumentNullException (nameof (InputProcessor.DriverName));
+        string type = GetInputProcessor ().DriverName ?? throw new InvalidOperationException ("Driver name is not set.");
 
         return type;
     }
@@ -143,8 +146,12 @@ internal class DriverImpl : IDriver
 
     private readonly IOutput _output;
 
+    public IOutput GetOutput () => _output;
+
+    private readonly IInputProcessor _inputProcessor;
+
     /// <inheritdoc/>
-    public IInputProcessor InputProcessor { get; }
+    public IInputProcessor GetInputProcessor () => _inputProcessor;
 
     /// <inheritdoc/>
     public IOutputBuffer OutputBuffer { get; }
@@ -157,7 +164,7 @@ internal class DriverImpl : IDriver
 
     private void CreateClipboard ()
     {
-        if (InputProcessor.DriverName is { } && InputProcessor.DriverName.Contains ("fake"))
+        if (GetInputProcessor ().DriverName is { } && GetInputProcessor ()!.DriverName!.Contains ("fake"))
         {
             if (Clipboard is null)
             {
@@ -414,7 +421,7 @@ internal class DriverImpl : IDriver
     public event EventHandler<Key>? KeyUp;
 
     /// <inheritdoc/>
-    public void EnqueueKeyEvent (Key key) { InputProcessor.EnqueueKeyDownEvent (key); }
+    public void EnqueueKeyEvent (Key key) { GetInputProcessor ().EnqueueKeyDownEvent (key); }
 
     #endregion Input Events
 

+ 20 - 20
Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs

@@ -7,29 +7,29 @@ namespace Terminal.Gui.Drivers;
 /// </summary>
 public class FakeOutput : OutputBase, IOutput
 {
-    private readonly StringBuilder _output = new ();
+   // private readonly StringBuilder _outputStringBuilder = new ();
     private int _cursorLeft;
     private int _cursorTop;
     private Size _consoleSize = new (80, 25);
+    private IOutputBuffer? _lastBuffer;
 
     /// <summary>
     /// 
     /// </summary>
     public FakeOutput ()
     {
-        LastBuffer = new OutputBufferImpl ();
-        LastBuffer.SetSize (80, 25);
+        _lastBuffer = new OutputBufferImpl ();
+        _lastBuffer.SetSize (80, 25);
     }
 
     /// <summary>
-    ///     Gets or sets the last output buffer written.
+    ///     Gets or sets the last output buffer written. The <see cref="IOutputBuffer.Contents"/> contains
+    ///     a reference to the buffer last written with <see cref="Write(IOutputBuffer)"/>.
     /// </summary>
-    public IOutputBuffer? LastBuffer { get; set; }
+    public IOutputBuffer? GetLastBuffer () => _lastBuffer;
 
-    /// <summary>
-    ///     Gets the captured output as a string.
-    /// </summary>
-    public string Output => _output.ToString ();
+    ///// <inheritdoc cref="IOutput.GetLastOutput"/>
+    //public override string GetLastOutput () => _outputStringBuilder.ToString ();
 
     /// <inheritdoc />
     public Point GetCursorPosition ()
@@ -61,28 +61,28 @@ public class FakeOutput : OutputBase, IOutput
     /// <inheritdoc/>
     public void Write (ReadOnlySpan<char> text)
     {
-        _output.Append (text);
+//        _outputStringBuilder.Append (text);
     }
 
-    /// <inheritdoc cref="IDriver"/>
+    /// <inheritdoc cref="IOutput.Write(IOutputBuffer)"/>
     public override void Write (IOutputBuffer buffer)
     {
-        LastBuffer = buffer;
+        _lastBuffer = buffer;
         base.Write (buffer);
     }
 
+    ///// <inheritdoc/>
+    //protected override void Write (StringBuilder output)
+    //{
+    //    _outputStringBuilder.Append (output);
+    //}
+
     /// <inheritdoc cref="IDriver"/>
     public override void SetCursorVisibility (CursorVisibility visibility)
     {
         // Capture but don't act on it in fake output
     }
 
-    /// <inheritdoc/>
-    public void Dispose ()
-    {
-        // Nothing to dispose
-    }
-
     /// <inheritdoc/>
     protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
     {
@@ -123,8 +123,8 @@ public class FakeOutput : OutputBase, IOutput
     }
 
     /// <inheritdoc/>
-    protected override void Write (StringBuilder output)
+    public void Dispose ()
     {
-        _output.Append (output);
+        // Nothing to dispose
     }
 }

+ 6 - 1
Terminal.Gui/Drivers/IDriver.cs

@@ -61,7 +61,12 @@ public interface IDriver : IDisposable
     ///     e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
     ///     and detecting and processing ansi escape sequences.
     /// </summary>
-    IInputProcessor InputProcessor { get; }
+    IInputProcessor GetInputProcessor ();
+
+    /// <summary>
+    ///     Gets the output handler responsible for writing to the terminal.
+    /// </summary>
+    IOutput GetOutput ();
 
     /// <summary>Get the operating system clipboard.</summary>
     IClipboard? Clipboard { get; }

+ 6 - 0
Terminal.Gui/Drivers/IOutput.cs

@@ -65,6 +65,12 @@ public interface IOutput : IDisposable
     /// <param name="buffer"></param>
     void Write (IOutputBuffer buffer);
 
+    /// <summary>
+    ///     Gets a string containing the ANSI escape sequences and content most recently written
+    ///     to the terminal via <see cref="Write(IOutputBuffer)"/>
+    /// </summary>
+    string GetLastOutput ();
+
     /// <summary>
     ///     Generates an ANSI escape sequence string representation of the given <paramref name="buffer"/> contents.
     ///     This is the same output that would be written to the terminal to recreate the current screen contents.

+ 41 - 19
Terminal.Gui/Drivers/OutputBase.cs

@@ -56,19 +56,27 @@ public abstract class OutputBase
     /// <param name="visibility"></param>
     public abstract void SetCursorVisibility (CursorVisibility visibility);
 
-    /// <inheritdoc cref="IOutput.Write(IOutputBuffer)"/>
+    StringBuilder _lastOutputStringBuilder = new ();
+
+    /// <summary>
+    ///     Writes dirty cells from the buffer to the console. Hides cursor, iterates rows/cols,
+    ///     skips clean cells, batches dirty cells into ANSI sequences, wraps URLs with OSC 8,
+    ///     then renders sixel images. Cursor visibility is managed by <c>ApplicationMainLoop.SetCursor()</c>.
+    /// </summary>
     public virtual void Write (IOutputBuffer buffer)
     {
-        var top = 0;
-        var left = 0;
+        StringBuilder outputStringBuilder = new ();
+        int top = 0;
+        int left = 0;
         int rows = buffer.Rows;
         int cols = buffer.Cols;
-        var output = new StringBuilder ();
         Attribute? redrawAttr = null;
         int lastCol = -1;
 
+        // Hide cursor during rendering to prevent flicker
         SetCursorVisibility (CursorVisibility.Invisible);
 
+        // Process each row
         for (int row = top; row < rows; row++)
         {
             if (!SetCursorPositionImpl (0, row))
@@ -76,20 +84,24 @@ public abstract class OutputBase
                 return;
             }
 
-            output.Clear ();
+            outputStringBuilder.Clear ();
 
+            // Process columns in row
             for (int col = left; col < cols; col++)
             {
                 lastCol = -1;
                 var outputWidth = 0;
 
+                // Batch consecutive dirty cells
                 for (; col < cols; col++)
                 {
+                    // Skip clean cells - position cursor and continue
                     if (!buffer.Contents! [row, col].IsDirty)
                     {
-                        if (output.Length > 0)
+                        if (outputStringBuilder.Length > 0)
                         {
-                            WriteToConsole (output, ref lastCol, ref outputWidth);
+                            // This clears outputStringBuilder
+                            WriteToConsole (outputStringBuilder, ref lastCol, ref outputWidth);
                         }
                         else if (lastCol == -1)
                         {
@@ -111,24 +123,26 @@ public abstract class OutputBase
                         lastCol = col;
                     }
 
+                    // Append dirty cell as ANSI and mark clean
                     Cell cell = buffer.Contents [row, col];
                     buffer.Contents [row, col].IsDirty = false;
-                    AppendCellAnsi (cell, output, ref redrawAttr, ref _redrawTextStyle, cols, ref col, ref outputWidth);
+                    AppendCellAnsi (cell, outputStringBuilder, ref redrawAttr, ref _redrawTextStyle, cols, ref col, ref outputWidth);
                 }
             }
 
-            if (output.Length > 0)
+            // Flush buffered output for row
+            if (outputStringBuilder.Length > 0)
             {
                 if (IsLegacyConsole)
                 {
-                    Write (output);
+                    Write (outputStringBuilder);
                 }
                 else
                 {
                     SetCursorPositionImpl (lastCol, row);
 
-                    // Wrap URLs with OSC 8 hyperlink sequences using the new Osc8UrlLinker
-                    StringBuilder processed = Osc8UrlLinker.WrapOsc8 (output);
+                    // Wrap URLs with OSC 8 hyperlink sequences
+                    StringBuilder processed = Osc8UrlLinker.WrapOsc8 (outputStringBuilder);
                     Write (processed);
                 }
             }
@@ -139,6 +153,7 @@ public abstract class OutputBase
             return;
         }
 
+        // Render queued sixel images
         foreach (SixelToRender s in GetSixels ())
         {
             if (string.IsNullOrWhiteSpace (s.SixelData))
@@ -150,12 +165,12 @@ public abstract class OutputBase
             Write ((StringBuilder)new (s.SixelData));
         }
 
-
-        // DO NOT restore cursor visibility here - let ApplicationMainLoop.SetCursor() handle it
-        // The old code was saving/restoring visibility which caused flickering because
-        // it would restore to the old value even if the application wanted it hidden
+        // Cursor visibility restored by ApplicationMainLoop.SetCursor() to prevent flicker
     }
 
+    /// <inheritdoc cref="IOutput.GetLastOutput" />
+    public virtual string GetLastOutput () => _lastOutputStringBuilder.ToString ();
+
     /// <summary>
     ///     Changes the color and text style of the console to the given <paramref name="attr"/> and
     ///     <paramref name="redrawTextStyle"/>.
@@ -180,7 +195,10 @@ public abstract class OutputBase
     ///     Output the contents of the <paramref name="output"/> to the console.
     /// </summary>
     /// <param name="output"></param>
-    protected abstract void Write (StringBuilder output);
+    protected virtual void Write (StringBuilder output)
+    {
+        _lastOutputStringBuilder.Append (output);
+    }
 
     /// <summary>
     ///     Builds ANSI escape sequences for the specified rectangular region of the buffer.
@@ -273,7 +291,7 @@ public abstract class OutputBase
     /// <returns>A string containing ANSI escape sequences representing the buffer contents.</returns>
     public string ToAnsi (IOutputBuffer buffer)
     {
-        var output = new StringBuilder ();
+        StringBuilder output = new ();
         Attribute? lastAttr = null;
 
         BuildAnsiForRegion (buffer, 0, buffer.Rows, 0, buffer.Cols, output, ref lastAttr);
@@ -281,6 +299,10 @@ public abstract class OutputBase
         return output.ToString ();
     }
 
+    /// <summary>
+    ///     Writes buffered output to console, wrapping URLs with OSC 8 hyperlinks (non-legacy only),
+    ///     then clears the buffer and advances <paramref name="lastCol"/> by <paramref name="outputWidth"/>.
+    /// </summary>
     private void WriteToConsole (StringBuilder output, ref int lastCol, ref int outputWidth)
     {
         if (IsLegacyConsole)
@@ -289,7 +311,7 @@ public abstract class OutputBase
         }
         else
         {
-            // Wrap URLs with OSC 8 hyperlink sequences using the new Osc8UrlLinker
+            // Wrap URLs with OSC 8 hyperlink sequences
             StringBuilder processed = Osc8UrlLinker.WrapOsc8 (output);
             Write (processed);
         }

+ 125 - 92
Terminal.Gui/Drivers/OutputBufferImpl.cs

@@ -14,7 +14,7 @@ public class OutputBufferImpl : IOutputBuffer
     ///     UpdateScreen is called.
     ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
     /// </summary>
-    public Cell [,]? Contents { get; set; } = new Cell[0, 0];
+    public Cell [,]? Contents { get; set; } = new Cell [0, 0];
 
     private int _cols;
     private int _rows;
@@ -66,7 +66,7 @@ public class OutputBufferImpl : IOutputBuffer
     public virtual int Top { get; set; } = 0;
 
     /// <summary>
-    /// Indicates which lines have been modified and need to be redrawn.
+    ///     Indicates which lines have been modified and need to be redrawn.
     /// </summary>
     public bool [] DirtyLines { get; set; } = [];
 
@@ -138,116 +138,149 @@ public class OutputBufferImpl : IOutputBuffer
     {
         foreach (string grapheme in GraphemeHelper.GetGraphemes (str))
         {
-            string text = grapheme;
+            AddGrapheme (grapheme);
+        }
+    }
 
-            if (Contents is null)
-            {
-                return;
-            }
+    /// <summary>
+    ///     Adds a single grapheme to the display at the current cursor position.
+    /// </summary>
+    /// <param name="grapheme">The grapheme to add.</param>
+    private void AddGrapheme (string grapheme)
+    {
+        if (Contents is null)
+        {
+            return;
+        }
 
-            Clip ??= new (Screen);
+        Clip ??= new (Screen);
+        Rectangle clipRect = Clip!.GetBounds ();
 
-            Rectangle clipRect = Clip!.GetBounds ();
+        string text = grapheme;
+        int textWidth = -1;
 
-            int textWidth = -1;
-            bool validLocation = false;
+        lock (Contents)
+        {
+            bool validLocation = IsValidLocation (text, Col, Row);
 
-            lock (Contents)
+            if (validLocation)
             {
-                // Validate location inside the lock to prevent race conditions
-                validLocation = IsValidLocation (text, Col, Row);
-
-                if (validLocation)
-                {
-                    text = text.MakePrintable ();
-                    textWidth = text.GetColumns ();
-
-                    Contents [Row, Col].Attribute = CurrentAttribute;
-                    Contents [Row, Col].IsDirty = true;
+                text = text.MakePrintable ();
+                textWidth = text.GetColumns ();
 
-                    if (Col > 0)
-                    {
-                        // Check if cell to left has a wide glyph
-                        if (Contents [Row, Col - 1].Grapheme.GetColumns () > 1)
-                        {
-                            // Invalidate cell to left
-                            Contents [Row, Col - 1].Grapheme = Rune.ReplacementChar.ToString ();
-                            Contents [Row, Col - 1].IsDirty = true;
-                        }
-                    }
+                // Set attribute and mark dirty for current cell
+                Contents [Row, Col].Attribute = CurrentAttribute;
+                Contents [Row, Col].IsDirty = true;
 
-                    if (textWidth is 0 or 1)
-                    {
-                        Contents [Row, Col].Grapheme = text;
+                InvalidateOverlappedWideGlyph ();
 
-                        if (Col < clipRect.Right - 1 && Col + 1 < Cols)
-                        {
-                            Contents [Row, Col + 1].IsDirty = true;
-                        }
-                    }
-                    else if (textWidth == 2)
-                    {
-                        if (!Clip.Contains (Col + 1, Row))
-                        {
-                            // We're at the right edge of the clip, so we can't display a wide character.
-                            Contents [Row, Col].Grapheme = Rune.ReplacementChar.ToString ();
-                        }
-                        else if (!Clip.Contains (Col, Row))
-                        {
-                            // Our 1st column is outside the clip, so we can't display a wide character.
-                            if (Col + 1 < Cols)
-                            {
-                                Contents [Row, Col + 1].Grapheme = Rune.ReplacementChar.ToString ();
-                            }
-                        }
-                        else
-                        {
-                            Contents [Row, Col].Grapheme = text;
-
-                            if (Col < clipRect.Right - 1 && Col + 1 < Cols)
-                            {
-                                // Invalidate cell to right so that it doesn't get drawn
-                                Contents [Row, Col + 1].Grapheme = Rune.ReplacementChar.ToString ();
-                                Contents [Row, Col + 1].IsDirty = true;
-                            }
-                        }
-                    }
-                    else
-                    {
-                        // This is a non-spacing character, so we don't need to do anything
-                        Contents [Row, Col].Grapheme = " ";
-                        Contents [Row, Col].IsDirty = false;
-                    }
+                WriteGraphemeByWidth (text, textWidth, clipRect);
 
-                    DirtyLines [Row] = true;
-                }
+                DirtyLines [Row] = true;
             }
 
+            // Always advance cursor (even if location was invalid)
+            // Keep Col/Row updates inside the lock to prevent race conditions
             Col++;
 
             if (textWidth > 1)
             {
-                Debug.Assert (textWidth <= 2);
+                // Skip the second column of a wide character
+                // IMPORTANT: We do NOT modify column N+1's IsDirty or Attribute here.
+                // See: https://github.com/gui-cs/Terminal.Gui/issues/4258
+                Col++;
+            }
+        }
+    }
 
-                if (validLocation)
-                {
-                    lock (Contents!)
-                    {
-                        // Re-validate Col is still in bounds after increment
-                        if (Col < Cols && Row < Rows && Col < clipRect.Right)
-                        {
-                            // This is a double-width character, and we are not at the end of the line.
-                            // Col now points to the second column of the character. Ensure it doesn't
-                            // Get rendered.
-                            Contents [Row, Col].IsDirty = false;
-                            Contents [Row, Col].Attribute = CurrentAttribute;
-                        }
-                    }
-                }
+    /// <summary>
+    ///     If we're writing at an odd column and there's a wide glyph to our left,
+    ///     invalidate it since we're overwriting the second half.
+    /// </summary>
+    private void InvalidateOverlappedWideGlyph ()
+    {
+        if (Col > 0 && Contents! [Row, Col - 1].Grapheme.GetColumns () > 1)
+        {
+            Contents [Row, Col - 1].Grapheme = Rune.ReplacementChar.ToString ();
+            Contents [Row, Col - 1].IsDirty = true;
+        }
+    }
 
-                Col++;
+    /// <summary>
+    ///     Writes a grapheme to the buffer based on its width (0, 1, or 2 columns).
+    /// </summary>
+    /// <param name="text">The printable text to write.</param>
+    /// <param name="textWidth">The column width of the text.</param>
+    /// <param name="clipRect">The clipping rectangle.</param>
+    private void WriteGraphemeByWidth (string text, int textWidth, Rectangle clipRect)
+    {
+        switch (textWidth)
+        {
+            case 0:
+            case 1:
+                WriteSingleWidthGrapheme (text, clipRect);
+
+                break;
+
+            case 2:
+                WriteWideGrapheme (text);
+
+                break;
+
+            default:
+                // Negative width or non-spacing character (shouldn't normally occur)
+                Contents! [Row, Col].Grapheme = " ";
+                Contents [Row, Col].IsDirty = false;
+
+                break;
+        }
+    }
+
+    /// <summary>
+    ///     Writes a single-width character (0 or 1 column wide).
+    /// </summary>
+    private void WriteSingleWidthGrapheme (string text, Rectangle clipRect)
+    {
+        Contents! [Row, Col].Grapheme = text;
+
+        // Mark the next cell as dirty to ensure proper rendering of adjacent content
+        if (Col < clipRect.Right - 1 && Col + 1 < Cols)
+        {
+            Contents [Row, Col + 1].IsDirty = true;
+        }
+    }
+
+    /// <summary>
+    ///     Writes a wide character (2 columns wide) handling clipping and partial overlap cases.
+    /// </summary>
+    private void WriteWideGrapheme (string text)
+    {
+        if (!Clip!.Contains (Col + 1, Row))
+        {
+            // Second column is outside clip - can't fit wide char here
+            Contents! [Row, Col].Grapheme = Rune.ReplacementChar.ToString ();
+        }
+        else if (!Clip.Contains (Col, Row))
+        {
+            // First column is outside clip but second isn't
+            // Mark second column as replacement to indicate partial overlap
+            if (Col + 1 < Cols)
+            {
+                Contents! [Row, Col + 1].Grapheme = Rune.ReplacementChar.ToString ();
             }
         }
+        else
+        {
+            // Both columns are in bounds - write the wide character
+            // It will naturally render across both columns when output to the terminal
+            Contents! [Row, Col].Grapheme = text;
+
+            // DO NOT modify column N+1 here!
+            // The wide glyph will naturally render across both columns.
+            // If we set column N+1 to replacement char, we would overwrite
+            // any content that was intentionally drawn there (like borders at odd columns).
+            // See: https://github.com/gui-cs/Terminal.Gui/issues/4258
+        }
     }
 
     /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>

+ 1 - 0
Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs

@@ -66,6 +66,7 @@ internal class UnixOutput : OutputBase, IOutput
     /// <inheritdoc />
     protected override void Write (StringBuilder output)
     {
+        base.Write (output);
         try
         {
             byte [] utf8 = Encoding.UTF8.GetBytes (output.ToString ());

+ 3 - 1
Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs

@@ -184,7 +184,8 @@ internal partial class WindowsOutput : OutputBase, IOutput
 
         if (!WriteConsole (!IsLegacyConsole ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
         {
-            throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer.");
+            // Don't throw in unit tests
+            // throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer.");
         }
     }
 
@@ -318,6 +319,7 @@ internal partial class WindowsOutput : OutputBase, IOutput
         {
             return;
         }
+        base.Write (output);
 
         var str = output.ToString ();
 

+ 1 - 0
Terminal.Gui/ViewBase/Adornment/Border.cs

@@ -214,6 +214,7 @@ public partial class Border : Adornment
             // TODO: all this.
             return Parent?.SuperView?.BorderStyle ?? LineStyle.None;
         }
+        // BUGBUG: Setting LineStyle should SetNeedsDraw
         set => _lineStyle = value;
     }
 

+ 132 - 34
Terminal.Gui/ViewBase/View.Drawing.cs

@@ -6,7 +6,7 @@ namespace Terminal.Gui.ViewBase;
 public partial class View // Drawing APIs
 {
     /// <summary>
-    ///     Draws a set of views.
+    ///     Draws a set of peer views (views that share the same SuperView).
     /// </summary>
     /// <param name="views">The peer views to draw.</param>
     /// <param name="force">If <see langword="true"/>, <see cref="View.SetNeedsDraw()"/> will be called on each view to force it to be drawn.</param>
@@ -39,8 +39,8 @@ public partial class View // Drawing APIs
 
         // After all peer views have been drawn and cleared, we can now clear the SuperView's SubViewNeedsDraw flag.
         // ClearNeedsDraw() does not clear SuperView.SubViewNeedsDraw (by design, to avoid premature clearing
-        // when siblings still need drawing), so we must do it here after ALL peers are processed.
-        // We only clear the flag if ALL the SuperView's subviews no longer need drawing.
+        // when peer subviews still need drawing), so we must do it here after ALL peers are processed.
+        // We only clear the flag if ALL the SuperView's SubViews no longer need drawing.
         View? lastSuperView = null;
         foreach (View view in viewsArray)
         {
@@ -85,8 +85,8 @@ public partial class View // Drawing APIs
         if (NeedsDraw || SubViewNeedsDraw)
         {
             // ------------------------------------
-            // Draw the Border and Padding.
-            // Note Margin with a Shadow is special-cased and drawn in a separate pass to support
+            // Draw the Border and Padding Adornments.
+            // Note: Margin with a Shadow is special-cased and drawn in a separate pass to support
             // transparent shadows.
             DoDrawAdornments (originalClip);
             SetClip (originalClip);
@@ -106,7 +106,7 @@ public partial class View // Drawing APIs
             DoClearViewport (context);
 
             // ------------------------------------
-            // Draw the subviews first (order matters: SubViews, Text, Content)
+            // Draw the SubViews first (order matters: SubViews, Text, Content)
             if (SubViewNeedsDraw)
             {
                 DoDrawSubViews (context);
@@ -130,8 +130,8 @@ public partial class View // Drawing APIs
             DoRenderLineCanvas (context);
 
             // ------------------------------------
-            // Re-draw the border and padding subviews
-            // HACK: This is a hack to ensure that the border and padding subviews are drawn after the line canvas.
+            // Re-draw the Border and Padding Adornment SubViews
+            // HACK: This is a hack to ensure that the Border and Padding Adornment SubViews are drawn after the line canvas.
             DoDrawAdornmentsSubViews ();
 
             // ------------------------------------
@@ -170,15 +170,20 @@ public partial class View // Drawing APIs
         SetClip (originalClip);
 
         // ------------------------------------
-        // We're done drawing - The Clip is reset to what it was before we started.
+        // We're done drawing - The Clip is reset to what it was before we started
+        // But the context contains the region that was drawn by this view
         DoDrawComplete (context);
+
+        // When DoDrawComplete returns, Driver.Clip has been updated to exclude this view's area.
+        // The next view drawn (earlier in Z-order, typically a peer view or the SuperView) will see
+        // a clip with "holes" where this view (and any SubViews drawn before it) are located.
     }
 
     #region DrawAdornments
 
     private void DoDrawAdornmentsSubViews ()
     {
-        // NOTE: We do not support subviews of Margin?
+        // NOTE: We do not support SubViews of Margin
 
         if (Border?.SubViews is { } && Border.Thickness != Thickness.Empty && Border.NeedsDraw)
         {
@@ -302,7 +307,7 @@ public partial class View // Drawing APIs
     /// <summary>
     ///     Called when the View's Adornments are to be drawn. Prepares <see cref="View.LineCanvas"/>. If
     ///     <see cref="SuperViewRendersLineCanvas"/> is true, only the
-    ///     <see cref="LineCanvas"/> of this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
+    ///     <see cref="LineCanvas"/> of this view's SubViews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
     ///     false (the default), this method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
     /// </summary>
     /// <returns><see langword="true"/> to stop further drawing of the Adornments.</returns>
@@ -481,7 +486,7 @@ public partial class View // Drawing APIs
                                  Rectangle.Empty);
         }
 
-        // We assume that the text has been drawn over the entire area; ensure that the subviews are redrawn.
+        // We assume that the text has been drawn over the entire area; ensure that the SubViews are redrawn.
         SetSubViewNeedsDrawDownHierarchy ();
     }
 
@@ -571,7 +576,7 @@ public partial class View // Drawing APIs
     ///         such as <see cref="View.AddRune(int, int, Rune)"/>, <see cref="View.AddStr(string)"/>, and <see cref="View.FillRect(Rectangle, Rune)"/>.
     ///     </para>
     ///     <para>
-    ///         The event is invoked after <see cref="ClearingViewport"/> and <see cref="Text"/> have been drawn, but before any <see cref="SubViews"/> are drawn.
+    ///         The event is invoked after <see cref="ClearingViewport"/> and <see cref="Text"/> have been drawn, but after <see cref="SubViews"/> have been drawn.
     ///     </para>
     ///     <para>
     ///         <b>Transparency Support:</b> If the View has <see cref="ViewportSettings"/> with <see cref="ViewportSettingsFlags.Transparent"/>
@@ -650,7 +655,8 @@ public partial class View // Drawing APIs
             return;
         }
 
-        // Draw the subviews in reverse order to leverage clipping.
+        // Draw the SubViews in reverse Z-order to leverage clipping.
+        // SubViews earlier in the collection are drawn last (on top).
         foreach (View view in InternalSubViews.Snapshot ().Where (v => v.Visible).Reverse ())
         {
             // TODO: HACK - This forcing of SetNeedsDraw with SuperViewRendersLineCanvas enables auto line join to work, but is brute force.
@@ -691,23 +697,22 @@ public partial class View // Drawing APIs
     /// <returns><see langword="true"/> to stop further drawing of <see cref="LineCanvas"/>.</returns>
     protected virtual bool OnRenderingLineCanvas () { return false; }
 
-    /// <summary>The canvas that any line drawing that is to be shared by subviews of this view should add lines to.</summary>
+    /// <summary>The canvas that any line drawing that is to be shared by SubViews of this view should add lines to.</summary>
     /// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
     public LineCanvas LineCanvas { get; } = new ();
 
     /// <summary>
-    ///     Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for rendering any
-    ///     lines. If <see langword="true"/> the rendering of any borders drawn by this Frame will be done by its parent's
+    ///     Gets or sets whether this View will use its SuperView's <see cref="LineCanvas"/> for rendering any
+    ///     lines. If <see langword="true"/> the rendering of any borders drawn by this view will be done by its
     ///     SuperView. If <see langword="false"/> (the default) this View's <see cref="OnDrawingAdornments"/> method will
-    ///     be
-    ///     called to render the borders.
+    ///     be called to render the borders.
     /// </summary>
     public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
 
     /// <summary>
     ///     Causes the contents of <see cref="LineCanvas"/> to be drawn.
     ///     If <see cref="SuperViewRendersLineCanvas"/> is true, only the
-    ///     <see cref="LineCanvas"/> of this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
+    ///     <see cref="LineCanvas"/> of this view's SubViews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
     ///     false (the default), this method will cause the <see cref="LineCanvas"/> to be rendered.
     /// </summary>
     /// <param name="context"></param>
@@ -732,7 +737,7 @@ public partial class View // Drawing APIs
                     AddStr (p.Value.Value.Grapheme);
 
                     // Add each drawn cell to the context
-                    context?.AddDrawnRectangle (new Rectangle (p.Key, new (1, 1)) );
+                    //context?.AddDrawnRectangle (new Rectangle (p.Key, new (1, 1)) );
                 }
             }
 
@@ -744,60 +749,153 @@ public partial class View // Drawing APIs
 
     #region DrawComplete
 
+    /// <summary>
+    ///     Called at the end of <see cref="Draw(DrawContext)"/> to finalize drawing and update the clip region.
+    /// </summary>
+    /// <param name="context">
+    ///     The <see cref="DrawContext"/> tracking what regions were drawn by this view and its subviews.
+    ///     May be <see langword="null"/> if not tracking drawn regions.
+    /// </param>
     private void DoDrawComplete (DrawContext? context)
     {
+        // Phase 1: Notify that drawing is complete
+        // Raise virtual method first, then event. This allows subclasses to override behavior
+        // before subscribers see the event.
         OnDrawComplete (context);
         DrawComplete?.Invoke (this, new (Viewport, Viewport, context));
 
-        // Now, update the clip to exclude this view (not including Margin)
+        // Phase 2: Update Driver.Clip to exclude this view's drawn area
+        // This prevents views "behind" this one (earlier in draw order/Z-order) from drawing over it.
+        // Adornments (Margin, Border, Padding) are handled by their Adornment.Parent view and don't exclude themselves.
         if (this is not Adornment)
         {
             if (ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent))
             {
-                // context!.DrawnRegion is the region that was drawn by this view. It may include regions outside
-                // the Viewport. We need to clip it to the Viewport.
+                // Transparent View Path:
+                // Only exclude the regions that were actually drawn, allowing views beneath
+                // to show through in areas where nothing was drawn.
+
+                // The context.DrawnRegion may include areas outside the Viewport (e.g., if content
+                // was drawn with ViewportSettingsFlags.AllowContentOutsideViewport). We need to clip
+                // it to the Viewport bounds to prevent excluding areas that aren't visible.
                 context!.ClipDrawnRegion (ViewportToScreen (Viewport));
 
-                // Exclude the drawn region from the clip
+                // Exclude the actually-drawn region from Driver.Clip
                 ExcludeFromClip (context.GetDrawnRegion ());
 
-                // Exclude the Border and Padding from the clip
+                // Border and Padding are always opaque (they draw lines/fills), so exclude them too
                 ExcludeFromClip (Border?.Thickness.AsRegion (Border.FrameToScreen ()));
                 ExcludeFromClip (Padding?.Thickness.AsRegion (Padding.FrameToScreen ()));
             }
             else
             {
-                // Exclude this view (not including Margin) from the Clip
+                // Opaque View Path (default):
+                // Exclude the entire view area from Driver.Clip. This is the typical case where
+                // the view is considered fully opaque.
+
+                // Start with the Frame in screen coordinates
                 Rectangle borderFrame = FrameToScreen ();
 
+                // If there's a Border, use its frame instead (includes the border thickness)
                 if (Border is { })
                 {
                     borderFrame = Border.FrameToScreen ();
                 }
 
-                // In the non-transparent (typical case), we want to exclude the entire view area (borderFrame) from the clip
+                // Exclude this view's entire area (Border inward, but not Margin) from the clip.
+                // This prevents any view drawn after this one from drawing in this area.
                 ExcludeFromClip (borderFrame);
 
-                // Update context.DrawnRegion to include the entire view (borderFrame), but clipped to our SuperView's viewport
-                // This enables the SuperView to know what was drawn by this view.
+                // Update the DrawContext to track that we drew this entire rectangle.
+                // This allows our SuperView (if any) to know what area we occupied,
+                // which is important for transparency calculations at higher levels.
                 context?.AddDrawnRectangle (borderFrame);
             }
         }
 
-        // TODO: Determine if we need another event that conveys the FINAL DrawContext
+        // When this method returns, Driver.Clip has been updated to exclude this view's area.
+        // The next view drawn (earlier in Z-order, typically a peer view or the SuperView) will see
+        // a clip with "holes" where this view (and any SubViews drawn before it) are located.
     }
 
     /// <summary>
-    ///     Called when the View is completed drawing.
+    ///     Called when the View has completed drawing and is about to update the clip region.
     /// </summary>
+    /// <param name="context">
+    ///     The <see cref="DrawContext"/> containing the regions that were drawn by this view and its subviews.
+    ///     May be <see langword="null"/> if not tracking drawn regions.
+    /// </param>
     /// <remarks>
-    ///     The <paramref name="context"/> parameter provides the drawn region of the View.
+    ///     <para>
+    ///         This method is called at the very end of <see cref="Draw(DrawContext)"/>, after all drawing
+    ///         (adornments, content, text, subviews, line canvas) has completed but before the view's area
+    ///         is excluded from <see cref="IDriver.Clip"/>.
+    ///     </para>
+    ///     <para>
+    ///         Use this method to:
+    ///     </para>
+    ///     <list type="bullet">
+    ///         <item>
+    ///             <description>Perform any final drawing operations that need to happen after SubViews are drawn</description>
+    ///         </item>
+    ///         <item>
+    ///             <description>Inspect what was drawn via the <paramref name="context"/> parameter</description>
+    ///         </item>
+    ///         <item>
+    ///             <description>Add additional regions to the <paramref name="context"/> if needed</description>
+    ///         </item>
+    ///     </list>
+    ///     <para>
+    ///         <b>Important:</b> At this point, <see cref="IDriver.Clip"/> has been restored to the state
+    ///         it was in when <see cref="Draw(DrawContext)"/> began. After this method returns, the view's
+    ///         area will be excluded from the clip (see <see cref="DoDrawComplete"/> for details).
+    ///     </para>
+    ///     <para>
+    ///         <b>Transparency Support:</b> If <see cref="ViewportSettings"/> includes
+    ///         <see cref="ViewportSettingsFlags.Transparent"/>, the <paramref name="context"/> parameter
+    ///         contains the actual regions that were drawn. You can inspect this to see what areas
+    ///         will be excluded from the clip, and optionally add more regions if needed.
+    ///     </para>
     /// </remarks>
+    /// <seealso cref="DrawComplete"/>
+    /// <seealso cref="Draw(DrawContext)"/>
+    /// <seealso cref="DoDrawComplete"/>
     protected virtual void OnDrawComplete (DrawContext? context) { }
 
-    /// <summary>Raised when the View is completed drawing.</summary>
+    /// <summary>Raised when the View has completed drawing and is about to update the clip region.</summary>
     /// <remarks>
+    ///     <para>
+    ///         This event is raised at the very end of <see cref="Draw(DrawContext)"/>, after all drawing
+    ///         operations have completed but before the view's area is excluded from <see cref="IDriver.Clip"/>.
+    ///     </para>
+    ///     <para>
+    ///         The <see cref="DrawEventArgs.DrawContext"/> property provides information about what regions
+    ///         were drawn by this view and its subviews. This is particularly useful for views with
+    ///         <see cref="ViewportSettingsFlags.Transparent"/> enabled, as it shows exactly which areas
+    ///         will be excluded from the clip.
+    ///     </para>
+    ///     <para>
+    ///         Use this event to:
+    ///     </para>
+    ///     <list type="bullet">
+    ///         <item>
+    ///             <description>Perform any final drawing operations</description>
+    ///         </item>
+    ///         <item>
+    ///             <description>Inspect what was drawn</description>
+    ///         </item>
+    ///         <item>
+    ///             <description>Track drawing statistics or metrics</description>
+    ///         </item>
+    ///     </list>
+    ///     <para>
+    ///         <b>Note:</b> This event fires <i>after</i> <see cref="OnDrawComplete(DrawContext)"/>. If you need
+    ///         to override the behavior, prefer overriding the virtual method in a subclass rather than
+    ///         subscribing to this event.
+    ///     </para>
     /// </remarks>
+    /// <seealso cref="OnDrawComplete(DrawContext)"/>
+    /// <seealso cref="Draw(DrawContext)"/>
     public event EventHandler<DrawEventArgs>? DrawComplete;
 
     #endregion DrawComplete

+ 1 - 1
Tests/TerminalGuiFluentTesting/GuiTestContext.Input.cs

@@ -64,7 +64,7 @@ public partial class GuiTestContext
             {
                 mouseEvent.Position = mouseEvent.ScreenPosition;
 
-                app.Driver.InputProcessor.EnqueueMouseEvent (app, mouseEvent);
+                app.Driver.GetInputProcessor ().EnqueueMouseEvent (app, mouseEvent);
             }
             else
             {

+ 129 - 1
Tests/UnitTests/DriverAssert.cs

@@ -47,7 +47,7 @@ internal partial class DriverAssert
         {
             driver = Application.Driver;
         }
-        ArgumentNullException.ThrowIfNull(driver);
+        ArgumentNullException.ThrowIfNull (driver);
 
         Cell [,] contents = driver!.Contents!;
 
@@ -193,6 +193,134 @@ internal partial class DriverAssert
         Assert.Equal (expectedLook, actualLook);
     }
 
+#pragma warning disable xUnit1013 // Public method should be marked as test
+    /// <summary>Asserts that the driver raw ANSI output matches the expected output.</summary>
+    /// <param name="expectedLook">Expected output with C# escape sequences (e.g., \x1b for ESC)</param>
+    /// <param name="output"></param>
+    /// <param name="driver">The IDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
+    public static void AssertDriverOutputIs (
+        string expectedLook,
+        ITestOutputHelper output,
+        IDriver? driver = null
+    )
+    {
+#pragma warning restore xUnit1013 // Public method should be marked as test
+        if (driver is null && ApplicationImpl.ModelUsage == ApplicationModelUsage.LegacyStatic)
+        {
+            driver = Application.Driver;
+        }
+        ArgumentNullException.ThrowIfNull (driver);
+
+        string? actualLook = driver.GetOutput().GetLastOutput ();
+
+        // Unescape the expected string to convert C# escape sequences like \x1b to actual characters
+        string unescapedExpected = UnescapeString (expectedLook);
+
+        // Trim trailing whitespace from actual (screen padding)
+        actualLook = actualLook.TrimEnd ();
+        unescapedExpected = unescapedExpected.TrimEnd ();
+
+        if (string.Equals (unescapedExpected, actualLook))
+        {
+            return;
+        }
+
+        // If test is about to fail show user what things looked like
+        if (!string.Equals (unescapedExpected, actualLook))
+        {
+            output?.WriteLine ($"Expected (length={unescapedExpected.Length}):" + Environment.NewLine + unescapedExpected);
+            output?.WriteLine ($" But Was (length={actualLook.Length}):" + Environment.NewLine + actualLook);
+
+            // Show the difference at the end
+            int minLen = Math.Min (unescapedExpected.Length, actualLook.Length);
+            output?.WriteLine ($"Lengths: Expected={unescapedExpected.Length}, Actual={actualLook.Length}, MinLen={minLen}");
+            if (actualLook.Length > unescapedExpected.Length)
+            {
+                output?.WriteLine ($"Actual has {actualLook.Length - unescapedExpected.Length} extra characters at the end");
+            }
+        }
+
+        Assert.Equal (unescapedExpected, actualLook);
+    }
+
+    /// <summary>
+    ///     Unescapes a C# string literal by processing escape sequences like \x1b, \n, \r, \t, etc.
+    /// </summary>
+    /// <param name="input">String with C# escape sequences</param>
+    /// <returns>String with escape sequences converted to actual characters</returns>
+    private static string UnescapeString (string input)
+    {
+        if (string.IsNullOrEmpty (input))
+        {
+            return input;
+        }
+
+        var result = new StringBuilder (input.Length);
+        int i = 0;
+
+        while (i < input.Length)
+        {
+            if (input [i] == '\\' && i + 1 < input.Length)
+            {
+                char next = input [i + 1];
+
+                switch (next)
+                {
+                    case 'x' when i + 3 < input.Length:
+                        // Handle \xHH (2-digit hex)
+                        string hex = input.Substring (i + 2, 2);
+                        if (int.TryParse (hex, System.Globalization.NumberStyles.HexNumber, null, out int hexValue))
+                        {
+                            result.Append ((char)hexValue);
+                            i += 4; // Skip \xHH
+                            continue;
+                        }
+                        break;
+
+                    case 'n':
+                        result.Append ('\n');
+                        i += 2;
+                        continue;
+
+                    case 'r':
+                        result.Append ('\r');
+                        i += 2;
+                        continue;
+
+                    case 't':
+                        result.Append ('\t');
+                        i += 2;
+                        continue;
+
+                    case '\\':
+                        result.Append ('\\');
+                        i += 2;
+                        continue;
+
+                    case '"':
+                        result.Append ('"');
+                        i += 2;
+                        continue;
+
+                    case '\'':
+                        result.Append ('\'');
+                        i += 2;
+                        continue;
+
+                    case '0':
+                        result.Append ('\0');
+                        i += 2;
+                        continue;
+                }
+            }
+
+            // Not an escape sequence, add the character as-is
+            result.Append (input [i]);
+            i++;
+        }
+
+        return result.ToString ();
+    }
     /// <summary>
     ///     Asserts that the driver contents are equal to the provided string.
     /// </summary>

+ 32 - 4
Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs

@@ -3,14 +3,10 @@ using System.Text;
 using UnitTests;
 using Xunit.Abstractions;
 
-// Alias Console to MockConsole so we don't accidentally use Console
-
 namespace DriverTests;
 
 public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
 {
-    private readonly ITestOutputHelper _output = output;
-
     [Fact]
     public void AddRune ()
     {
@@ -179,4 +175,36 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
 
         driver.Dispose ();
     }
+
+    [Fact]
+    public void AddStr_Glyph_On_Second_Cell_Of_Wide_Glyph_Outputs_Correctly ()
+    {
+        IDriver? driver = CreateFakeDriver ();
+        driver.SetScreenSize (6, 3);
+
+        driver!.Clip = new (driver.Screen);
+
+        driver.Move (1, 0);
+        driver.AddStr ("┌");
+        driver.Move (2, 0);
+        driver.AddStr ("─");
+        driver.Move (3, 0);
+        driver.AddStr ("┐");
+        driver.Clip.Exclude (new Region (new (1, 0, 3, 1)));
+
+        driver.Move (0, 0);
+        driver.AddStr ("🍎🍎🍎🍎");
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              �┌─┐🍎
+                                              """,
+                                              output,
+                                              driver);
+
+        driver.Refresh ();
+
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m�┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
+                                           output, driver);
+    }
 }

+ 1 - 0
Tests/UnitTestsParallelizable/Drivers/ClipRegionTests.cs

@@ -1,4 +1,5 @@
 #nullable enable
+using System.Text;
 using UnitTests;
 using Xunit.Abstractions;
 

+ 45 - 0
Tests/UnitTestsParallelizable/Drivers/DriverTests.cs

@@ -92,6 +92,51 @@ public class DriverTests (ITestOutputHelper output) : FakeDriverBase
 
         app.Dispose ();
     }
+
+    // Tests fix for https://github.com/gui-cs/Terminal.Gui/issues/4258
+    [Theory]
+    [InlineData ("fake")]
+    [InlineData ("windows")]
+    [InlineData ("dotnet")]
+    [InlineData ("unix")]
+    public void All_Drivers_When_Clipped_AddStr_Glyph_On_Second_Cell_Of_Wide_Glyph_Outputs_Correctly (string driverName)
+    {
+        IApplication? app = Application.Create ();
+        app.Init (driverName);
+        IDriver driver = app.Driver!;
+
+        // Need to force "windows" driver to override legacy console mode for this test
+        driver.IsLegacyConsole = false;
+        driver.Force16Colors = false;
+
+        driver.SetScreenSize (6, 3);
+
+        driver!.Clip = new (driver.Screen);
+
+        driver.Move (1, 0);
+        driver.AddStr ("┌");
+        driver.Move (2, 0);
+        driver.AddStr ("─");
+        driver.Move (3, 0);
+        driver.AddStr ("┐");
+        driver.Clip.Exclude (new Region (new (1, 0, 3, 1)));
+
+        driver.Move (0, 0);
+        driver.AddStr ("🍎🍎🍎🍎");
+
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              �┌─┐🍎
+                                              """,
+                                              output,
+                                              driver);
+
+        driver.Refresh ();
+
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m�┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
+                                           output, driver);
+    }
 }
 
 public class TestTop : Runnable

+ 55 - 44
Tests/UnitTestsParallelizable/Drivers/OutputBaseTests.cs

@@ -1,6 +1,4 @@
-#nullable enable
-
-namespace DriverTests;
+namespace DriverTests;
 
 public class OutputBaseTests
 {
@@ -9,7 +7,7 @@ public class OutputBaseTests
     {
         // Arrange
         var output = new FakeOutput ();
-        IOutputBuffer buffer = output.LastBuffer!;
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
         buffer.SetSize (1, 1);
 
         // Act
@@ -32,21 +30,21 @@ public class OutputBaseTests
 
         // Create DriverImpl and associate it with the FakeOutput to test Sixel output
         IDriver driver = new DriverImpl (
-                                 new FakeInputProcessor (null!),
-                                 new OutputBufferImpl (),
-                                 output,
-                                 new (new AnsiResponseParser ()),
-                                 new SizeMonitorImpl (output));
+                                         new FakeInputProcessor (null!),
+                                         new OutputBufferImpl (),
+                                         output,
+                                         new (new AnsiResponseParser ()),
+                                         new SizeMonitorImpl (output));
 
         driver.Force16Colors = force16Colors;
 
-        IOutputBuffer buffer = output.LastBuffer!;
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
         buffer.SetSize (1, 1);
 
         // Use a known RGB color and attribute
         var fg = new Color (1, 2, 3);
         var bg = new Color (4, 5, 6);
-        buffer.CurrentAttribute = new Attribute (fg, bg);
+        buffer.CurrentAttribute = new (fg, bg);
         buffer.AddStr ("X");
 
         // Act
@@ -59,7 +57,7 @@ public class OutputBaseTests
         }
         else if (!isLegacyConsole && force16Colors)
         {
-            var expected16 = EscSeqUtils.CSI_SetForegroundColor (fg.GetAnsiColorCode ());
+            string expected16 = EscSeqUtils.CSI_SetForegroundColor (fg.GetAnsiColorCode ());
             Assert.Contains (expected16, ansi);
         }
         else
@@ -78,7 +76,7 @@ public class OutputBaseTests
     {
         // Arrange
         var output = new FakeOutput ();
-        IOutputBuffer buffer = output.LastBuffer!;
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
         buffer.SetSize (2, 1);
 
         // Mark two characters as dirty by writing them into the buffer
@@ -92,7 +90,7 @@ public class OutputBaseTests
         output.Write (buffer); // calls OutputBase.Write via FakeOutput
 
         // Assert: content was written to the fake output and dirty flags cleared
-        Assert.Contains ("AB", output.Output);
+        Assert.Contains ("AB", output.GetLastOutput ());
         Assert.False (buffer.Contents! [0, 0].IsDirty);
         Assert.False (buffer.Contents! [0, 1].IsDirty);
     }
@@ -105,7 +103,7 @@ public class OutputBaseTests
         // Arrange
         // FakeOutput exposes this because it's in test scope
         var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
-        IOutputBuffer buffer = output.LastBuffer!;
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
         buffer.SetSize (3, 1);
 
         // Write 'A' at col 0 and 'C' at col 2; leave col 1 untouched (not dirty)
@@ -122,15 +120,15 @@ public class OutputBaseTests
         output.Write (buffer);
 
         // Assert: both characters were written (use Contains to avoid CI side effects)
-        Assert.Contains ("A", output.Output);
-        Assert.Contains ("C", output.Output);
+        Assert.Contains ("A", output.GetLastOutput ());
+        Assert.Contains ("C", output.GetLastOutput ());
 
         // Dirty flags cleared for the written cells
         Assert.False (buffer.Contents! [0, 0].IsDirty);
         Assert.False (buffer.Contents! [0, 2].IsDirty);
 
         // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
-        Assert.Equal (new Point (0, 0), output.GetCursorPosition ());
+        Assert.Equal (new (0, 0), output.GetCursorPosition ());
 
         // Now write 'X' at col 0 to verify subsequent writes also work
         buffer.Move (0, 0);
@@ -143,15 +141,15 @@ public class OutputBaseTests
         output.Write (buffer);
 
         // Assert: both characters were written (use Contains to avoid CI side effects)
-        Assert.Contains ("A", output.Output);
-        Assert.Contains ("C", output.Output);
+        Assert.Contains ("A", output.GetLastOutput ());
+        Assert.Contains ("C", output.GetLastOutput ());
 
         // Dirty flags cleared for the written cells
         Assert.False (buffer.Contents! [0, 0].IsDirty);
         Assert.False (buffer.Contents! [0, 2].IsDirty);
 
         // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
-        Assert.Equal (new Point (2, 0), output.GetCursorPosition ());
+        Assert.Equal (new (2, 0), output.GetCursorPosition ());
     }
 
     [Theory]
@@ -162,44 +160,57 @@ public class OutputBaseTests
         // Arrange
         // FakeOutput exposes this because it's in test scope
         var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
-        IOutputBuffer buffer = output.LastBuffer!;
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
         buffer.SetSize (3, 1);
 
-        // Write '🦮' at col 0 and 'A' at col 3; leave col 1 untouched (not dirty)
+        // Write '🦮' at col 0 and 'A' at col 2
         buffer.Move (0, 0);
         buffer.AddStr ("🦮A");
 
-        // Confirm some dirtiness before to write
+        // After the fix for https://github.com/gui-cs/Terminal.Gui/issues/4258:
+        // Writing a wide glyph at column 0 no longer sets column 1 to IsDirty = false.
+        // Column 1 retains whatever state it had (in this case, it was initialized as dirty
+        // by ClearContents, but may have been cleared by a previous Write call).
+        //
+        // What we care about is that wide glyphs work correctly and don't prevent
+        // other content from being drawn at odd columns.
         Assert.True (buffer.Contents! [0, 0].IsDirty);
-        Assert.False (buffer.Contents! [0, 1].IsDirty);
+
+        // Column 1 state depends on whether it was cleared by a previous Write - don't assert
         Assert.True (buffer.Contents! [0, 2].IsDirty);
 
         // Act
         output.Write (buffer);
 
-        Assert.Contains ("🦮", output.Output);
-        Assert.Contains ("A", output.Output);
+        Assert.Contains ("🦮", output.GetLastOutput ());
+        Assert.Contains ("A", output.GetLastOutput ());
 
         // Dirty flags cleared for the written cells
+        // Column 0 was written (wide glyph)
         Assert.False (buffer.Contents! [0, 0].IsDirty);
-        Assert.False (buffer.Contents! [0, 1].IsDirty);
+
+        // Column 1 was skipped by OutputBase.Write because column 0 had a wide glyph
+        // So its dirty flag remains true (it was initialized as dirty by ClearContents)
+        Assert.True (buffer.Contents! [0, 1].IsDirty);
+
+        // Column 2 was written ('A')
         Assert.False (buffer.Contents! [0, 2].IsDirty);
 
         Assert.Equal (new (0, 0), output.GetCursorPosition ());
 
-        // Now write 'X' at col 1 which replaces with the replacement character the col 0
+        // Now write 'X' at col 1 which invalidates the wide glyph at col 0
         buffer.Move (1, 0);
         buffer.AddStr ("X");
 
         // Confirm dirtiness state before to write
-        Assert.True (buffer.Contents! [0, 0].IsDirty);
-        Assert.True (buffer.Contents! [0, 1].IsDirty);
-        Assert.True (buffer.Contents! [0, 2].IsDirty);
+        Assert.True (buffer.Contents! [0, 0].IsDirty); // Invalidated by writing at col 1
+        Assert.True (buffer.Contents! [0, 1].IsDirty); // Just written
+        Assert.True (buffer.Contents! [0, 2].IsDirty); // Marked dirty by writing at col 1
 
         output.Write (buffer);
 
-        Assert.Contains ("�", output.Output);
-        Assert.Contains ("X", output.Output);
+        Assert.Contains ("�", output.GetLastOutput ());
+        Assert.Contains ("X", output.GetLastOutput ());
 
         // Dirty flags cleared for the written cells
         Assert.False (buffer.Contents! [0, 0].IsDirty);
@@ -217,7 +228,7 @@ public class OutputBaseTests
     {
         // Arrange
         var output = new FakeOutput ();
-        IOutputBuffer buffer = output.LastBuffer!;
+        IOutputBuffer buffer = output.GetLastBuffer ()!;
         buffer.SetSize (1, 1);
 
         // Ensure the buffer has some content so Write traverses rows
@@ -227,16 +238,16 @@ public class OutputBaseTests
         var s = new SixelToRender
         {
             SixelData = "SIXEL-DATA",
-            ScreenPosition = new Point (4, 2)
+            ScreenPosition = new (4, 2)
         };
 
         // Create DriverImpl and associate it with the FakeOutput to test Sixel output
         IDriver driver = new DriverImpl (
-                                 new FakeInputProcessor (null!),
-                                 new OutputBufferImpl (),
-                                 output,
-                                 new (new AnsiResponseParser ()),
-                                 new SizeMonitorImpl (output));
+                                         new FakeInputProcessor (null!),
+                                         new OutputBufferImpl (),
+                                         output,
+                                         new (new AnsiResponseParser ()),
+                                         new SizeMonitorImpl (output));
 
         // Add the Sixel to the driver
         driver.GetSixels ().Enqueue (s);
@@ -250,7 +261,7 @@ public class OutputBaseTests
         if (!isLegacyConsole)
         {
             // Assert: Sixel data was emitted (use Contains to avoid equality/side-effects)
-            Assert.Contains ("SIXEL-DATA", output.Output);
+            Assert.Contains ("SIXEL-DATA", output.GetLastOutput ());
 
             // Cursor was moved to Sixel position
             Assert.Equal (s.ScreenPosition, output.GetCursorPosition ());
@@ -258,7 +269,7 @@ public class OutputBaseTests
         else
         {
             // Assert: Sixel data was NOT emitted
-            Assert.DoesNotContain ("SIXEL-DATA", output.Output);
+            Assert.DoesNotContain ("SIXEL-DATA", output.GetLastOutput ());
 
             // Cursor was NOT moved to Sixel position
             Assert.NotEqual (s.ScreenPosition, output.GetCursorPosition ());
@@ -271,4 +282,4 @@ public class OutputBaseTests
 
         app.Dispose ();
     }
-}
+}

+ 337 - 74
Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs

@@ -1,19 +1,18 @@
-#nullable enable
+using System.Text;
 using UnitTests;
 using Xunit.Abstractions;
 
 namespace ViewBaseTests.Drawing;
 
-public class ViewDrawingClippingTests () : FakeDriverBase
+public class ViewDrawingClippingTests (ITestOutputHelper output) : FakeDriverBase
 {
     #region GetClip / SetClip Tests
 
-
     [Fact]
     public void GetClip_ReturnsDriverClip ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var region = new Region (new Rectangle (10, 10, 20, 20));
+        IDriver driver = CreateFakeDriver ();
+        var region = new Region (new (10, 10, 20, 20));
         driver.Clip = region;
         View view = new () { Driver = driver };
 
@@ -26,8 +25,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void SetClip_NullRegion_DoesNothing ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var original = new Region (new Rectangle (5, 5, 10, 10));
+        IDriver driver = CreateFakeDriver ();
+        var original = new Region (new (5, 5, 10, 10));
         driver.Clip = original;
 
         View view = new () { Driver = driver };
@@ -40,8 +39,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void SetClip_ValidRegion_SetsDriverClip ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var region = new Region (new Rectangle (10, 10, 30, 30));
+        IDriver driver = CreateFakeDriver ();
+        var region = new Region (new (10, 10, 30, 30));
         View view = new () { Driver = driver };
 
         view.SetClip (region);
@@ -56,8 +55,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void SetClipToScreen_ReturnsPreviousClip ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var original = new Region (new Rectangle (5, 5, 10, 10));
+        IDriver driver = CreateFakeDriver ();
+        var original = new Region (new (5, 5, 10, 10));
         driver.Clip = original;
         View view = new () { Driver = driver };
 
@@ -70,7 +69,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void SetClipToScreen_SetsClipToScreen ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
+        IDriver driver = CreateFakeDriver ();
         View view = new () { Driver = driver };
 
         view.SetClipToScreen ();
@@ -87,15 +86,15 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     public void ExcludeFromClip_Rectangle_NullDriver_DoesNotThrow ()
     {
         View view = new () { Driver = null };
-        var exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (5, 5, 10, 10)));
+        Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (5, 5, 10, 10)));
         Assert.Null (exception);
     }
 
     [Fact]
     public void ExcludeFromClip_Rectangle_ExcludesArea ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (new Rectangle (0, 0, 80, 25));
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (new (0, 0, 80, 25));
         View view = new () { Driver = driver };
 
         var toExclude = new Rectangle (10, 10, 20, 20);
@@ -111,19 +110,18 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     {
         View view = new () { Driver = null };
 
-        var exception = Record.Exception (() => view.ExcludeFromClip (new Region (new Rectangle (5, 5, 10, 10))));
+        Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Region (new (5, 5, 10, 10))));
         Assert.Null (exception);
     }
 
     [Fact]
     public void ExcludeFromClip_Region_ExcludesArea ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (new Rectangle (0, 0, 80, 25));
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (new (0, 0, 80, 25));
         View view = new () { Driver = driver };
 
-
-        var toExclude = new Region (new Rectangle (10, 10, 20, 20));
+        var toExclude = new Region (new (10, 10, 20, 20));
         view.ExcludeFromClip (toExclude);
 
         // Verify the region was excluded
@@ -150,8 +148,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void AddFrameToClip_IntersectsWithFrame ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
@@ -171,7 +169,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
         Assert.NotNull (driver.Clip);
 
         // The clip should now be the intersection of the screen and the view's frame
-        Rectangle expectedBounds = new Rectangle (1, 1, 20, 20);
+        var expectedBounds = new Rectangle (1, 1, 20, 20);
         Assert.Equal (expectedBounds, driver.Clip.GetBounds ());
     }
 
@@ -194,8 +192,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void AddViewportToClip_IntersectsWithViewport ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
@@ -222,8 +220,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void AddViewportToClip_WithClipContentOnly_LimitsToVisibleContent ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
@@ -260,7 +258,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     public void ClipRegions_StackCorrectly_WithNestedViews ()
     {
         IDriver driver = CreateFakeDriver (100, 100);
-        driver.Clip = new Region (driver.Screen);
+        driver.Clip = new (driver.Screen);
 
         var superView = new View
         {
@@ -278,7 +276,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
             X = 5,
             Y = 5,
             Width = 30,
-            Height = 30,
+            Height = 30
         };
         superView.Add (view);
         superView.LayoutSubViews ();
@@ -296,14 +294,15 @@ public class ViewDrawingClippingTests () : FakeDriverBase
 
         // Restore superView clip
         view.SetClip (superViewClip);
+
         //   Assert.Equal (superViewBounds, driver.Clip.GetBounds ());
     }
 
     [Fact]
     public void ClipRegions_RespectPreviousClip ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var initialClip = new Region (new Rectangle (20, 20, 40, 40));
+        IDriver driver = CreateFakeDriver ();
+        var initialClip = new Region (new (20, 20, 40, 40));
         driver.Clip = initialClip;
 
         var view = new View
@@ -322,9 +321,9 @@ public class ViewDrawingClippingTests () : FakeDriverBase
 
         // The new clip should be the intersection of the initial clip and the view's frame
         Rectangle expected = Rectangle.Intersect (
-                                                   initialClip.GetBounds (),
-                                                   view.FrameToScreen ()
-                                                  );
+                                                  initialClip.GetBounds (),
+                                                  view.FrameToScreen ()
+                                                 );
 
         Assert.Equal (expected, driver.Clip.GetBounds ());
 
@@ -340,8 +339,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void AddFrameToClip_EmptyFrame_WorksCorrectly ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
@@ -364,18 +363,18 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void AddViewportToClip_EmptyViewport_WorksCorrectly ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
             X = 1,
             Y = 1,
-            Width = 1,  // Minimal size to have adornments
+            Width = 1, // Minimal size to have adornments
             Height = 1,
             Driver = driver
         };
-        view.Border!.Thickness = new Thickness (1);
+        view.Border!.Thickness = new (1);
         view.BeginInit ();
         view.EndInit ();
         view.LayoutSubViews ();
@@ -391,12 +390,12 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void ClipRegions_OutOfBounds_HandledCorrectly ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
-            X = 100,  // Outside screen bounds
+            X = 100, // Outside screen bounds
             Y = 100,
             Width = 20,
             Height = 20,
@@ -409,6 +408,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
         Region? previous = view.AddFrameToClip ();
 
         Assert.NotNull (previous);
+
         // The clip should be empty since the view is outside the screen
         Assert.True (driver.Clip.IsEmpty () || !driver.Clip.Contains (100, 100));
     }
@@ -420,8 +420,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void Clip_Set_BeforeDraw_ClipsDrawing ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        var clip = new Region (new Rectangle (10, 10, 10, 10));
+        IDriver driver = CreateFakeDriver ();
+        var clip = new Region (new (10, 10, 10, 10));
         driver.Clip = clip;
 
         var view = new View
@@ -445,8 +445,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void Draw_UpdatesDriverClip ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
@@ -464,14 +464,15 @@ public class ViewDrawingClippingTests () : FakeDriverBase
 
         // Clip should be updated to exclude the drawn view
         Assert.NotNull (driver.Clip);
+
         // Assert.False (driver.Clip.Contains (15, 15)); // Point inside the view should be excluded
     }
 
     [Fact]
     public void Draw_WithSubViews_ClipsCorrectly ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var superView = new View
         {
@@ -491,13 +492,277 @@ public class ViewDrawingClippingTests () : FakeDriverBase
 
         // Both superView and view should be excluded from clip
         Assert.NotNull (driver.Clip);
+
         //    Assert.False (driver.Clip.Contains (15, 15)); // Point in superView should be excluded
     }
 
+    /// <summary>
+    /// Tests that wide glyphs (🍎) are correctly clipped when overlapped by bordered subviews
+    /// at different column alignments (even vs odd). Demonstrates:
+    /// 1. Full clipping at even columns (X=0, X=2)
+    /// 2. Partial clipping at odd columns (X=1) resulting in half-glyphs (�)
+    /// 3. The recursive draw flow and clip exclusion mechanism
+    /// 
+    /// For detailed draw flow documentation, see ViewDrawingClippingTests.DrawFlow.md
+    /// </summary>
+    [Fact]
+    public void Draw_WithBorderSubView_DrawsCorrectly ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+        IDriver driver = app!.Driver!;
+        driver.SetScreenSize (30, 20);
+
+        driver!.Clip = new (driver.Screen);
+
+        var superView = new Runnable ()
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto () + 4,
+            Height = Dim.Auto () + 1,
+            Driver = driver
+        };
+
+        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;
+                                    };
+
+        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);
+        app.Begin (superView);
+        // Begin calls LayoutAndDraw, so no need to call it again here
+        // app.LayoutAndDraw();
+
+        DriverAssert.AssertDriverContentsAre (
+                                                       """
+                                                       🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                                       ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎
+                                                       ┆viewWithBorderAtX0┆🍎🍎🍎
+                                                       └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎
+                                                       🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                                       �┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎
+                                                       �┆viewWithBorderAtX1┆ 🍎🍎
+                                                       �└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎
+                                                       🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                                       🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎
+                                                       🍎┆viewWithBorderAtX2┆🍎🍎
+                                                       🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎
+                                                       🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                                       """,
+                                                       output,
+                                                       driver);
+
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┆viewWithBorderAtX0┆🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m�┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m�┆viewWithBorderAtX1┆ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m�└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┆viewWithBorderAtX2┆🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
+                                           output, driver);
+
+        DriverImpl? driverImpl = driver as DriverImpl;
+        FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
+
+        output.WriteLine ("Driver Output After Redraw:\n" + driver.GetOutput().GetLastOutput());
+
+        // BUGBUG: Border.set_LineStyle does not call SetNeedsDraw
+        viewWithBorderAtX1!.Border!.LineStyle = LineStyle.Single;
+        viewWithBorderAtX1.Border!.SetNeedsDraw ();
+        app.LayoutAndDraw ();
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                              ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎
+                                              ┆viewWithBorderAtX0┆🍎🍎🍎
+                                              └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎
+                                              🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                              �┌──────────────────┐ 🍎🍎
+                                              �│viewWithBorderAtX1│ 🍎🍎
+                                              �└──────────────────┘ 🍎🍎
+                                              🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                              🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎
+                                              🍎┆viewWithBorderAtX2┆🍎🍎
+                                              🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎
+                                              🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
+                                              """,
+                                              output,
+                                              driver);
+
+
+    }
+
+    [Fact]
+    public void Draw_WithBorderSubView_At_Col1_In_WideGlyph_DrawsCorrectly ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+        IDriver driver = app!.Driver!;
+        driver.SetScreenSize (6, 3);  // Minimal: 6 cols wide (3 for content + 2 for border + 1), 3 rows high (1 for content + 2 for border)
+
+        driver!.Clip = new (driver.Screen);
+
+        var superView = new Runnable ()
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+            Driver = driver
+        };
+
+        Rune codepoint = Glyphs.Apple;
+
+        superView.DrawingContent += (s, e) =>
+                                    {
+                                        View? view = s as View;
+                                        view?.AddStr (0, 0, "🍎🍎🍎🍎");
+                                        view?.AddStr (0, 1, "🍎🍎🍎🍎");
+                                        view?.AddStr (0, 2, "🍎🍎🍎🍎");
+                                        e.DrawContext?.AddDrawnRectangle (view!.Viewport);
+                                        e.Cancel = true;
+                                    };
+
+        // Minimal border at X=1 (odd column), Width=3, Height=3 (includes border)
+        var viewWithBorder = new View
+        {
+            Text = "X",
+            BorderStyle = LineStyle.Single,
+            X = 1,
+            Y = 0,
+            Width = 3,
+            Height = 3
+        };
+
+        superView.Add (viewWithBorder);
+        app.Begin (superView);
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              �┌─┐🍎
+                                              �│X│🍎
+                                              �└─┘🍎
+                                              """,
+                                              output,
+                                              driver);
+
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m�┌─┐🍎�│X│🍎�└─┘🍎",
+            output, driver);
+
+        DriverImpl? driverImpl = driver as DriverImpl;
+        FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
+
+        output.WriteLine ("Driver Output:\n" + fakeOutput!.GetLastOutput ());
+    }
+
+
+    [Fact]
+    public void Draw_WithBorderSubView_At_Col3_In_WideGlyph_DrawsCorrectly ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+        IDriver driver = app!.Driver!;
+        driver.SetScreenSize (6, 3);  // Screen: 6 cols wide, 3 rows high; enough for 3x3 border subview at col 3 plus content on the left
+
+        driver!.Clip = new (driver.Screen);
+
+        var superView = new Runnable ()
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+            Driver = driver
+        };
+
+        Rune codepoint = Glyphs.Apple;
+
+        superView.DrawingContent += (s, e) =>
+        {
+            View? view = s as View;
+            view?.AddStr (0, 0, "🍎🍎🍎🍎");
+            view?.AddStr (0, 1, "🍎🍎🍎🍎");
+            view?.AddStr (0, 2, "🍎🍎🍎🍎");
+            e.DrawContext?.AddDrawnRectangle (view!.Viewport);
+            e.Cancel = true;
+        };
+
+        // Minimal border at X=3 (odd column), Width=3, Height=3 (includes border)
+        var viewWithBorder = new View
+        {
+            Text = "X",
+            BorderStyle = LineStyle.Single,
+            X = 3,
+            Y = 0,
+            Width = 3,
+            Height = 3
+        };
+
+        superView.Add (viewWithBorder);
+        app.Begin (superView);
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              🍎�┌─┐
+                                              🍎�│X│
+                                              🍎�└─┘
+                                              """,
+                                              output,
+                                              driver);
+
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎�┌─┐🍎�│X│🍎�└─┘",
+            output, driver);
+
+        DriverImpl? driverImpl = driver as DriverImpl;
+        FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
+
+        output.WriteLine ("Driver Output:\n" + fakeOutput!.GetLastOutput ());
+    }
+
     [Fact]
     public void Draw_NonVisibleView_DoesNotUpdateClip ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
+        IDriver driver = CreateFakeDriver ();
         var originalClip = new Region (driver.Screen);
         driver.Clip = originalClip.Clone ();
 
@@ -522,8 +787,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void ExcludeFromClip_ExcludesRegion ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
@@ -542,13 +807,12 @@ public class ViewDrawingClippingTests () : FakeDriverBase
 
         Assert.NotNull (driver.Clip);
         Assert.False (driver.Clip.Contains (20, 20)); // Point inside excluded rect should not be in clip
-
     }
 
     [Fact]
     public void ExcludeFromClip_WithNullClip_DoesNotThrow ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
+        IDriver driver = CreateFakeDriver ();
         driver.Clip = null!;
 
         var view = new View
@@ -560,10 +824,9 @@ public class ViewDrawingClippingTests () : FakeDriverBase
             Driver = driver
         };
 
-        var exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (15, 15, 10, 10)));
+        Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (15, 15, 10, 10)));
 
         Assert.Null (exception);
-
     }
 
     #endregion
@@ -573,7 +836,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void SetClip_SetsDriverClip ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
+        IDriver driver = CreateFakeDriver ();
 
         var view = new View
         {
@@ -584,7 +847,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
             Driver = driver
         };
 
-        var newClip = new Region (new Rectangle (5, 5, 30, 30));
+        var newClip = new Region (new (5, 5, 30, 30));
         view.SetClip (newClip);
 
         Assert.Equal (newClip, driver.Clip);
@@ -593,8 +856,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact (Skip = "See BUGBUG in SetClip")]
     public void SetClip_WithNullClip_ClearsClip ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (new Rectangle (10, 10, 20, 20));
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (new (10, 10, 20, 20));
 
         var view = new View
         {
@@ -613,7 +876,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void Draw_Excludes_View_From_Clip ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
+        IDriver driver = CreateFakeDriver ();
         var originalClip = new Region (driver.Screen);
         driver.Clip = originalClip.Clone ();
 
@@ -641,8 +904,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void Draw_EmptyViewport_DoesNotCrash ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
@@ -652,13 +915,13 @@ public class ViewDrawingClippingTests () : FakeDriverBase
             Height = 1,
             Driver = driver
         };
-        view.Border!.Thickness = new Thickness (1);
+        view.Border!.Thickness = new (1);
         view.BeginInit ();
         view.EndInit ();
         view.LayoutSubViews ();
 
         // With border of 1, viewport should be empty (0x0 or negative)
-        var exception = Record.Exception (() => view.Draw ());
+        Exception? exception = Record.Exception (() => view.Draw ());
 
         Assert.Null (exception);
     }
@@ -666,8 +929,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void Draw_VeryLargeView_HandlesClippingCorrectly ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
@@ -681,7 +944,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
         view.EndInit ();
         view.LayoutSubViews ();
 
-        var exception = Record.Exception (() => view.Draw ());
+        Exception? exception = Record.Exception (() => view.Draw ());
 
         Assert.Null (exception);
     }
@@ -689,8 +952,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void Draw_NegativeCoordinates_HandlesClippingCorrectly ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
@@ -704,7 +967,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
         view.EndInit ();
         view.LayoutSubViews ();
 
-        var exception = Record.Exception (() => view.Draw ());
+        Exception? exception = Record.Exception (() => view.Draw ());
 
         Assert.Null (exception);
     }
@@ -712,8 +975,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
     [Fact]
     public void Draw_OutOfScreenBounds_HandlesClippingCorrectly ()
     {
-        IDriver driver = CreateFakeDriver (80, 25);
-        driver.Clip = new Region (driver.Screen);
+        IDriver driver = CreateFakeDriver ();
+        driver.Clip = new (driver.Screen);
 
         var view = new View
         {
@@ -727,7 +990,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
         view.EndInit ();
         view.LayoutSubViews ();
 
-        var exception = Record.Exception (() => view.Draw ());
+        Exception? exception = Record.Exception (() => view.Draw ());
 
         Assert.Null (exception);
     }