Browse Source

Adds `ViewportSettings.Transparent` (#3886)

Tig 4 months ago
parent
commit
98f70b2632
41 changed files with 5560 additions and 3963 deletions
  1. 9 8
      .gitattributes
  2. 948 152
      Terminal.Gui/Drawing/Region.cs
  3. 128 0
      Terminal.Gui/Drawing/RegionOp.cs
  4. 13 0
      Terminal.Gui/Drawing/Thickness.cs
  5. 271 1
      Terminal.Gui/Text/TextFormatter.cs
  6. 1 3
      Terminal.Gui/View/Adornment/Adornment.cs
  7. 20 20
      Terminal.Gui/View/Adornment/Border.cs
  8. 54 0
      Terminal.Gui/View/DrawContext.cs
  9. 16 2
      Terminal.Gui/View/DrawEventArgs.cs
  10. 15 1
      Terminal.Gui/View/View.Content.cs
  11. 14 4
      Terminal.Gui/View/View.Drawing.Clipping.cs
  12. 2 2
      Terminal.Gui/View/View.Drawing.Primitives.cs
  13. 187 83
      Terminal.Gui/View/View.Drawing.cs
  14. 12 2
      Terminal.Gui/View/View.Mouse.cs
  15. 32 10
      Terminal.Gui/View/ViewportSettings.cs
  16. 2 2
      Terminal.Gui/Views/CharMap/CharMap.cs
  17. 1 3
      Terminal.Gui/Views/TileView.cs
  18. 1 0
      Terminal.sln.DotSettings
  19. 21 6
      UICatalog/Scenarios/AllViewsTester.cs
  20. 77 9
      UICatalog/Scenarios/Arrangement.cs
  21. 371 0
      UICatalog/Scenarios/Editors/ViewportSettingsEditor.cs
  22. 397 0
      UICatalog/Scenarios/RegionScenario.cs
  23. 80 62
      UICatalog/Scenarios/TextAlignmentAndDirection.cs
  24. 118 0
      UICatalog/Scenarios/Transparent.cs
  25. 31 287
      UICatalog/Scenarios/ViewportSettings.cs
  26. 166 0
      UnitTests/Drawing/Region/DifferenceTests.cs
  27. 188 0
      UnitTests/Drawing/Region/MergeRectanglesTests.cs
  28. 893 0
      UnitTests/Drawing/Region/RegionTests.cs
  29. 264 0
      UnitTests/Drawing/Region/SubtractRectangleTests.cs
  30. 0 2147
      UnitTests/Drawing/RegionTests.cs
  31. 36 0
      UnitTests/View/ColorSchemeTests.cs
  32. 411 0
      UnitTests/View/Draw/ClearViewportTests.cs
  33. 303 0
      UnitTests/View/Draw/ClipTests.cs
  34. 32 0
      UnitTests/View/Draw/DrawEventTests.cs
  35. 263 376
      UnitTests/View/Draw/DrawTests.cs
  36. 60 0
      UnitTests/View/Draw/NeedsDrawTests.cs
  37. 107 0
      UnitTests/View/Draw/TransparentTests.cs
  38. 15 0
      UnitTests/View/TextTests.cs
  39. 1 783
      UnitTests/View/ViewTests.cs
  40. BIN
      local_packages/Terminal.Gui.2.0.0.nupkg
  41. BIN
      local_packages/Terminal.Gui.2.0.0.snupkg

+ 9 - 8
.gitattributes

@@ -1,13 +1,14 @@
-# Set the default behavior for all files.
+# Set default behavior to automatically normalize line endings.
 * text=auto
 
-# Normalized and converts to 
-# native line endings on checkout.
-*.cs text
-
-# Convert to LF line endings on checkout.
+# Explicitly declare text files you want to always be normalized and converted to native line endings on checkout.
+*.cs text eol=lf
+*.txt text eol=lf
+*.md text eol=lf
 *.sh text eol=lf
+*.ps1 text eol=lf
 
-# Binary files.
+# Denote all files that are truly binary and should not be modified.
 *.png binary
-*.jpg binary
+*.jpg binary
+*.gif binary

+ 948 - 152
Terminal.Gui/Drawing/Region.cs

@@ -2,166 +2,407 @@
 
 namespace Terminal.Gui;
 
-using System.Buffers;
-
 /// <summary>
-///     Represents a region composed of one or more rectangles, providing methods for union, intersection, exclusion, and
-///     complement operations.
+///     Represents a region composed of one or more rectangles, providing methods for geometric set operations such as
+///     union,
+///     intersection, exclusion, and complement. This class is designed for use in graphical or terminal-based user
+///     interfaces
+///     where regions need to be manipulated to manage screen areas, clipping, or drawing boundaries.
 /// </summary>
-public class Region : IDisposable
+/// <remarks>
+///     <para>
+///         This class is thread-safe. All operations are synchronized to ensure consistent state when accessed concurrently.
+///     </para>
+///     <para>
+///         The <see cref="Region"/> class adopts a philosophy of efficiency and flexibility, balancing performance with
+///         usability for GUI applications. It maintains a list of <see cref="Rectangle"/> objects, representing disjoint
+///         (non-overlapping) rectangular areas, and supports operations inspired by set theory. These operations allow
+///         combining regions in various ways, such as merging areas (<see cref="RegionOp.Union"/> or
+///         <see cref="RegionOp.MinimalUnion"/>),
+///         finding common areas (<see cref="RegionOp.Intersect"/>), or removing portions (
+///         <see cref="RegionOp.Difference"/> or
+///         <see cref="Exclude(Rectangle)"/>).
+///     </para>
+///     <para>
+///         To achieve high performance, the class employs a sweep-line algorithm for merging rectangles, which efficiently
+///         processes large sets of rectangles in O(n log n) time by scanning along the x-axis and tracking active vertical
+///         intervals. This approach ensures scalability for typical GUI scenarios with moderate numbers of rectangles. For
+///         operations like <see cref="RegionOp.Union"/> and <see cref="RegionOp.MinimalUnion"/>, an optional minimization
+///         step (
+///         <see
+///             cref="MinimizeRectangles"/>
+///         ) is used to reduce the number of rectangles to a minimal set, producing the smallest
+///         possible collection of non-overlapping rectangles that cover the same area. This minimization, while O(n²) in
+///         worst-case complexity, is optimized for small-to-medium collections and provides a compact representation ideal
+///         for drawing or logical operations.
+///     </para>
+///     <para>
+///         The class is immutable in its operations (returning new regions or modifying in-place via methods like
+///         <see cref="Combine(Rectangle,RegionOp)"/>), supports nullability for robustness, and implements
+///         <see cref="IDisposable"/> to manage
+///         resources by clearing internal state. Developers can choose between granular (detailed) or minimal (compact)
+///         outputs for union operations via <see cref="RegionOp.Union"/> and <see cref="RegionOp.MinimalUnion"/>, catering
+///         to diverse use cases such as rendering optimization, event handling, or visualization.
+///     </para>
+/// </remarks>
+public class Region
 {
-    private List<Rectangle> _rectangles;
+    private readonly List<Rectangle> _rectangles = [];
+
+    // Add a single reusable list for temp operations
+    private readonly List<Rectangle> _tempRectangles = new();
+
+    // Object used for synchronization
+    private readonly object _syncLock = new object();
 
     /// <summary>
     ///     Initializes a new instance of the <see cref="Region"/> class.
     /// </summary>
-    public Region () { _rectangles = new (); }
+    public Region () { }
 
     /// <summary>
     ///     Initializes a new instance of the <see cref="Region"/> class with the specified rectangle.
     /// </summary>
     /// <param name="rectangle">The initial rectangle for the region.</param>
-    public Region (Rectangle rectangle) { _rectangles = new () { rectangle }; }
+    public Region (Rectangle rectangle)
+    {
+        lock (_syncLock)
+        {
+            _rectangles.Add (rectangle);
+        }
+    }
 
     /// <summary>
-    ///     Adds the specified rectangle to the region.
+    ///     Creates an exact copy of the region.
     /// </summary>
-    /// <param name="rectangle">The rectangle to add to the region.</param>
-    public void Union (Rectangle rectangle)
+    /// <returns>A new <see cref="Region"/> that is a copy of this instance.</returns>
+    public Region Clone ()
     {
-        _rectangles.Add (rectangle);
-        _rectangles = MergeRectangles (_rectangles);
+        lock (_syncLock)
+        {
+            var clone = new Region ();
+            clone._rectangles.Capacity = _rectangles.Count; // Pre-allocate capacity
+            clone._rectangles.AddRange (_rectangles);
+
+            return clone;
+        }
     }
 
     /// <summary>
-    ///     Adds the specified region to this region.
+    ///     Combines <paramref name="rectangle"/> with the region using the specified operation.
     /// </summary>
-    /// <param name="region">The region to add to this region.</param>
-    public void Union (Region region)
+    /// <param name="rectangle">The rectangle to combine.</param>
+    /// <param name="operation">The operation to perform.</param>
+    public void Combine (Rectangle rectangle, RegionOp operation)
     {
-        _rectangles.AddRange (region._rectangles);
-        _rectangles = MergeRectangles (_rectangles);
+        lock (_syncLock)
+        {
+            if (rectangle.IsEmpty && operation != RegionOp.Replace)
+            {
+                if (operation == RegionOp.Intersect)
+                {
+                    _rectangles.Clear ();
+                }
+
+                return;
+            }
+
+            Combine (new Region (rectangle), operation);
+        }
     }
 
     /// <summary>
-    ///     Updates the region to be the intersection of itself with the specified rectangle.
+    ///     Combines <paramref name="region"/> with the region using the specified operation.
     /// </summary>
-    /// <param name="rectangle">The rectangle to intersect with the region.</param>
-    public void Intersect (Rectangle rectangle)
+    /// <param name="region">The region to combine.</param>
+    /// <param name="operation">The operation to perform.</param>
+    public void Combine (Region? region, RegionOp operation)
     {
-        if (_rectangles.Count == 0)
+        lock (_syncLock)
         {
+            CombineInternal(region, operation);
+        }
+    }
+
+    // Private method to implement the combine logic within a lock
+    private void CombineInternal(Region? region, RegionOp operation)
+    {
+        if (region is null || region._rectangles.Count == 0)
+        {
+            if (operation is RegionOp.Intersect or RegionOp.Replace)
+            {
+                _rectangles.Clear ();
+            }
+
             return;
         }
-        // TODO: In-place swap within the original list. Does order of intersections matter?
-        // Rectangle = 4 * i32 = 16 B
-        // ~128 B stack allocation
-        const int maxStackallocLength = 8;
-        Rectangle []? rentedArray = null;
-        try
+
+        switch (operation)
         {
-            Span<Rectangle> rectBuffer = _rectangles.Count <= maxStackallocLength
-                ? stackalloc Rectangle[maxStackallocLength]
-                : (rentedArray = ArrayPool<Rectangle>.Shared.Rent (_rectangles.Count));
+            case RegionOp.Difference:
 
-            _rectangles.CopyTo (rectBuffer);
-            ReadOnlySpan<Rectangle> rectangles = rectBuffer[.._rectangles.Count];
-            _rectangles.Clear ();
+                // region is regionB
+                // We'll chain the difference: (regionA - rect1) - rect2 - rect3 ...
+                List<Rectangle> newRectangles = new (_rectangles);
 
-            foreach (var rect in rectangles)
-            {
-                Rectangle intersection = Rectangle.Intersect (rect, rectangle);
-                if (!intersection.IsEmpty)
+                foreach (Rectangle rect in region._rectangles)
+                {
+                    List<Rectangle> temp = new ();
+
+                    foreach (Rectangle r in newRectangles)
+                    {
+                        temp.AddRange (SubtractRectangle (r, rect));
+                    }
+
+                    newRectangles = temp;
+                }
+
+                _rectangles.Clear ();
+                _rectangles.AddRange (newRectangles);
+
+                break;
+
+            case RegionOp.Intersect:
+                List<Rectangle> intersections = new (_rectangles.Count); // Pre-allocate
+
+                // Null is same as empty region
+                region ??= new ();
+
+                foreach (Rectangle rect1 in _rectangles)
                 {
-                    _rectangles.Add (intersection);
+                    foreach (Rectangle rect2 in region!._rectangles)
+                    {
+                        Rectangle intersected = Rectangle.Intersect (rect1, rect2);
+
+                        if (!intersected.IsEmpty)
+                        {
+                            intersections.Add (intersected);
+                        }
+                    }
                 }
-            }
+
+                _rectangles.Clear ();
+                _rectangles.AddRange (intersections);
+
+                break;
+
+            case RegionOp.Union:
+                // Avoid collection initialization with spread operator
+                _tempRectangles.Clear();
+                _tempRectangles.AddRange(_rectangles);
+                if (region != null)
+                {
+                    // Get the region's rectangles safely
+                    lock (region._syncLock)
+                    {
+                        _tempRectangles.AddRange(region._rectangles);
+                    }
+                }
+                List<Rectangle> mergedUnion = MergeRectangles(_tempRectangles, false);
+                _rectangles.Clear();
+                _rectangles.AddRange(mergedUnion);
+                break;
+
+            case RegionOp.MinimalUnion:
+                // Avoid collection initialization with spread operator
+                _tempRectangles.Clear();
+                _tempRectangles.AddRange(_rectangles);
+                if (region != null)
+                {
+                    // Get the region's rectangles safely
+                    lock (region._syncLock)
+                    {
+                        _tempRectangles.AddRange(region._rectangles);
+                    }
+                }
+                List<Rectangle> mergedMinimalUnion = MergeRectangles(_tempRectangles, true);
+                _rectangles.Clear();
+                _rectangles.AddRange(mergedMinimalUnion);
+                break;
+
+            case RegionOp.XOR:
+                Exclude (region);
+                region.Combine (this, RegionOp.Difference);
+                _rectangles.AddRange (region._rectangles);
+
+                break;
+
+            case RegionOp.ReverseDifference:
+                region.Combine (this, RegionOp.Difference);
+                _rectangles.Clear ();
+                _rectangles.AddRange (region._rectangles);
+
+                break;
+
+            case RegionOp.Replace:
+                _rectangles.Clear ();
+                _rectangles.Capacity = region._rectangles.Count; // Pre-allocate
+                _rectangles.AddRange (region._rectangles);
+
+                break;
+        }
+    }
+
+    /// <summary>
+    ///     Updates the region to be the complement of itself within the specified bounds.
+    /// </summary>
+    /// <param name="bounds">The bounding rectangle to use for complementing the region.</param>
+    public void Complement (Rectangle bounds)
+    {
+        if (bounds.IsEmpty || _rectangles.Count == 0)
+        {
+            _rectangles.Clear ();
+
+            return;
         }
-        finally
+
+        List<Rectangle> complementRectangles = new (4) { bounds }; // Typical max initial capacity
+
+        foreach (Rectangle rect in _rectangles)
         {
-            if (rentedArray != null)
-            {
-                ArrayPool<Rectangle>.Shared.Return (rentedArray);
-            }
+            complementRectangles = complementRectangles.SelectMany (r => SubtractRectangle (r, rect)).ToList ();
         }
+
+        _rectangles.Clear ();
+        _rectangles.AddRange (complementRectangles);
     }
 
     /// <summary>
-    ///     Updates the region to be the intersection of itself with the specified region.
+    ///     Determines whether the specified point is contained within the region.
     /// </summary>
-    /// <param name="region">The region to intersect with this region.</param>
-    public void Intersect (Region region)
+    /// <param name="x">The x-coordinate of the point.</param>
+    /// <param name="y">The y-coordinate of the point.</param>
+    /// <returns><c>true</c> if the point is contained within the region; otherwise, <c>false</c>.</returns>
+    public bool Contains (int x, int y)
     {
-        List<Rectangle> intersections = new List<Rectangle> ();
-
-        foreach (Rectangle rect1 in _rectangles)
+        lock (_syncLock)
         {
-            foreach (Rectangle rect2 in region._rectangles)
+            foreach (Rectangle r in _rectangles)
             {
-                Rectangle intersected = Rectangle.Intersect (rect1, rect2); 
-
-                if (!intersected.IsEmpty)
+                if (r.Contains (x, y))
                 {
-                    intersections.Add (intersected);
+                    return true;
                 }
             }
-        }
 
-        _rectangles = intersections;
+            return false;
+        }
     }
 
     /// <summary>
-    ///     Removes the specified rectangle from the region.
+    ///     Determines whether the specified rectangle is contained within the region.
     /// </summary>
-    /// <param name="rectangle">The rectangle to exclude from the region.</param>
-    public void Exclude (Rectangle rectangle) { _rectangles = _rectangles.SelectMany (r => SubtractRectangle (r, rectangle)).ToList (); }
+    /// <param name="rectangle">The rectangle to check for containment.</param>
+    /// <returns><c>true</c> if the rectangle is contained within the region; otherwise, <c>false</c>.</returns>
+    public bool Contains (Rectangle rectangle)
+    {
+        lock (_syncLock)
+        {
+            foreach (Rectangle r in _rectangles)
+            {
+                if (r.Contains (rectangle))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
 
     /// <summary>
-    ///     Removes the portion of the specified region from this region.
+    ///     Determines whether the specified object is equal to this region.
     /// </summary>
-    /// <param name="region">The region to exclude from this region.</param>
-    public void Exclude (Region region)
+    /// <param name="obj">The object to compare with this region.</param>
+    /// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
+    public override bool Equals (object? obj) { return obj is Region other && Equals (other); }
+
+    private static bool IsRegionEmpty (List<Rectangle> rectangles)
     {
-        foreach (Rectangle rect in region._rectangles)
+        if (rectangles.Count == 0)
         {
-            _rectangles = _rectangles.SelectMany (r => SubtractRectangle (r, rect)).ToList ();
+            return true;
+        }
+
+        foreach (Rectangle r in rectangles)
+        {
+            if (r is { IsEmpty: false, Width: > 0, Height: > 0 })
+            {
+                return false;
+            }
         }
+
+        return true;
     }
 
     /// <summary>
-    ///     Updates the region to be the complement of itself within the specified bounds.
+    ///     Determines whether the specified region is equal to this region.
     /// </summary>
-    /// <param name="bounds">The bounding rectangle to use for complementing the region.</param>
-    public void Complement (Rectangle bounds)
+    /// <param name="other">The region to compare with this region.</param>
+    /// <returns><c>true</c> if the regions are equal; otherwise, <c>false</c>.</returns>
+    public bool Equals (Region? other)
     {
-        if (bounds.IsEmpty || _rectangles.Count == 0)
+        if (other is null)
         {
-            _rectangles.Clear ();
+            return false;
+        }
 
-            return;
+        if (ReferenceEquals (this, other))
+        {
+            return true;
         }
 
-        List<Rectangle> complementRectangles = new List<Rectangle> { bounds };
+        // Check if either region is empty
+        bool thisEmpty = IsRegionEmpty (_rectangles);
+        bool otherEmpty = IsRegionEmpty (other._rectangles);
 
-        foreach (Rectangle rect in _rectangles)
+        // If either is empty, they're equal only if both are empty
+        if (thisEmpty || otherEmpty)
         {
-            complementRectangles = complementRectangles.SelectMany (r => SubtractRectangle (r, rect)).ToList ();
+            return thisEmpty == otherEmpty;
+        }
+
+        // For non-empty regions, compare rectangle counts
+        if (_rectangles.Count != other._rectangles.Count)
+        {
+            return false;
+        }
+
+        // Compare all rectangles - order matters since we maintain canonical form
+        for (var i = 0; i < _rectangles.Count; i++)
+        {
+            if (!_rectangles [i].Equals (other._rectangles [i]))
+            {
+                return false;
+            }
         }
 
-        _rectangles = complementRectangles;
+        return true;
     }
 
     /// <summary>
-    ///     Creates an exact copy of the region.
+    ///     Removes the specified rectangle from the region.
     /// </summary>
-    /// <returns>A new <see cref="Region"/> that is a copy of this instance.</returns>
-    public Region Clone ()
-    {
-        var clone = new Region ();
-        clone._rectangles = new (_rectangles);
+    /// <remarks>
+    ///     <para>
+    ///         This is a helper method that is equivalent to calling <see cref="Combine(Rectangle,RegionOp)"/> with
+    ///         <see cref="RegionOp.Difference"/>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="rectangle">The rectangle to exclude from the region.</param>
+    public void Exclude (Rectangle rectangle) { Combine (rectangle, RegionOp.Difference); }
 
-        return clone;
-    }
+    /// <summary>
+    ///     Removes the portion of the specified region from this region.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This is a helper method that is equivalent to calling <see cref="Combine(Region,RegionOp)"/> with
+    ///         <see cref="RegionOp.Difference"/>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="region">The region to exclude from this region.</param>
+    public void Exclude (Region? region) { Combine (region, RegionOp.Difference); }
 
     /// <summary>
     ///     Gets a bounding rectangle for the entire region.
@@ -174,114 +415,346 @@ public class Region : IDisposable
             return Rectangle.Empty;
         }
 
-        int left = _rectangles.Min (r => r.Left);
-        int top = _rectangles.Min (r => r.Top);
-        int right = _rectangles.Max (r => r.Right);
-        int bottom = _rectangles.Max (r => r.Bottom);
+        Rectangle first = _rectangles [0];
+        int left = first.Left;
+        int top = first.Top;
+        int right = first.Right;
+        int bottom = first.Bottom;
+
+        for (var i = 1; i < _rectangles.Count; i++)
+        {
+            Rectangle r = _rectangles [i];
+            left = Math.Min (left, r.Left);
+            top = Math.Min (top, r.Top);
+            right = Math.Max (right, r.Right);
+            bottom = Math.Max (bottom, r.Bottom);
+        }
 
         return new (left, top, right - left, bottom - top);
     }
 
     /// <summary>
-    ///     Determines whether the region is empty.
+    ///     Returns a hash code for this region.
     /// </summary>
-    /// <returns><c>true</c> if the region is empty; otherwise, <c>false</c>.</returns>
-    public bool IsEmpty () { return !_rectangles.Any (); }
+    /// <returns>A hash code for this region.</returns>
+    public override int GetHashCode ()
+    {
+        var hash = new HashCode ();
+
+        foreach (Rectangle rect in _rectangles)
+        {
+            hash.Add (rect);
+        }
+
+        return hash.ToHashCode ();
+    }
 
     /// <summary>
-    ///     Determines whether the specified point is contained within the region.
+    ///     Returns an array of rectangles that represent the region.
     /// </summary>
-    /// <param name="x">The x-coordinate of the point.</param>
-    /// <param name="y">The y-coordinate of the point.</param>
-    /// <returns><c>true</c> if the point is contained within the region; otherwise, <c>false</c>.</returns>
-    public bool Contains (int x, int y)
+    /// <returns>An array of <see cref="Rectangle"/> objects that make up the region.</returns>
+    public Rectangle [] GetRectangles () { return _rectangles.ToArray (); }
+
+    /// <summary>
+    ///     Updates the region to be the intersection of itself with the specified rectangle.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This is a helper method that is equivalent to calling <see cref="Combine(Rectangle,RegionOp)"/> with
+    ///         <see cref="RegionOp.Intersect"/>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="rectangle">The rectangle to intersect with the region.</param>
+    public void Intersect (Rectangle rectangle) { Combine (rectangle, RegionOp.Intersect); }
+
+    /// <summary>
+    ///     Updates the region to be the intersection of itself with the specified region.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This is a helper method that is equivalent to calling <see cref="Combine(Region,RegionOp)"/> with
+    ///         <see cref="RegionOp.Intersect"/>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="region">The region to intersect with this region.</param>
+    public void Intersect (Region? region) { Combine (region, RegionOp.Intersect); }
+
+    /// <summary>
+    ///     Determines whether the region is empty.
+    /// </summary>
+    /// <returns><c>true</c> if the region is empty; otherwise, <c>false</c>.</returns>
+    public bool IsEmpty ()
     {
-        foreach (var rect in _rectangles)
+        if (_rectangles.Count == 0)
         {
-            if (rect.Contains (x, y))
+            return true;
+        }
+
+        foreach (Rectangle r in _rectangles)
+        {
+            if (r is { IsEmpty: false, Width: > 0, Height: > 0 })
             {
-                return true;
+                return false;
             }
         }
-        return false;
+
+        return true;
     }
 
     /// <summary>
-    ///     Determines whether the specified rectangle is contained within the region.
+    ///     Translates all rectangles in the region by the specified offsets.
     /// </summary>
-    /// <param name="rectangle">The rectangle to check for containment.</param>
-    /// <returns><c>true</c> if the rectangle is contained within the region; otherwise, <c>false</c>.</returns>
-    public bool Contains (Rectangle rectangle)
+    /// <param name="offsetX">The amount to offset along the x-axis.</param>
+    /// <param name="offsetY">The amount to offset along the y-axis.</param>
+    public void Translate (int offsetX, int offsetY)
     {
-        foreach (var rect in _rectangles)
+        if (offsetX == 0 && offsetY == 0)
         {
-            if (rect.Contains (rectangle))
-            {
-                return true;
-            }
+            return;
+        }
+
+        for (var i = 0; i < _rectangles.Count; i++)
+        {
+            Rectangle rect = _rectangles [i];
+            _rectangles [i] = rect with { X = rect.Left + offsetX, Y = rect.Top + offsetY };
         }
-        return false;
     }
 
     /// <summary>
-    ///     Returns an array of rectangles that represent the region.
+    ///     Adds the specified rectangle to the region. Merges all rectangles into a minimal or granular bounding shape.
     /// </summary>
-    /// <returns>An array of <see cref="Rectangle"/> objects that make up the region.</returns>
-    public Rectangle [] GetRegionScans () { return _rectangles.ToArray (); }
+    /// <param name="rectangle">The rectangle to add to the region.</param>
+    public void Union (Rectangle rectangle) { Combine (rectangle, RegionOp.Union); }
 
     /// <summary>
-    ///     Offsets all rectangles in the region by the specified amounts.
+    ///     Adds the specified region to this region. Merges all rectangles into a minimal or granular bounding shape.
     /// </summary>
-    /// <param name="offsetX">The amount to offset along the x-axis.</param>
-    /// <param name="offsetY">The amount to offset along the y-axis.</param>
-    public void Offset (int offsetX, int offsetY)
+    /// <param name="region">The region to add to this region.</param>
+    public void Union (Region? region) { Combine (region, RegionOp.Union); }
+
+    /// <summary>
+    ///     Adds the specified rectangle to the region. Merges all rectangles into the smallest possible bounding shape.
+    /// </summary>
+    /// <param name="rectangle">The rectangle to add to the region.</param>
+    public void MinimalUnion (Rectangle rectangle) { Combine (rectangle, RegionOp.MinimalUnion); }
+
+    /// <summary>
+    ///     Adds the specified region to this region. Merges all rectangles into the smallest possible bounding shape.
+    /// </summary>
+    /// <param name="region">The region to add to this region.</param>
+    public void MinimalUnion (Region? region) { Combine (region, RegionOp.MinimalUnion); }
+
+    /// <summary>
+    ///     Merges overlapping rectangles into a minimal or granular set of non-overlapping rectangles with a minimal bounding
+    ///     shape.
+    /// </summary>
+    /// <param name="rectangles">The list of rectangles to merge.</param>
+    /// <param name="minimize">
+    ///     If <c>true</c>, minimizes the set to the smallest possible number of rectangles; otherwise,
+    ///     returns a granular set.
+    /// </param>
+    /// <returns>A list of merged rectangles.</returns>
+    internal static List<Rectangle> MergeRectangles (List<Rectangle> rectangles, bool minimize)
     {
-        for (int i = 0; i < _rectangles.Count; i++)
+        if (rectangles.Count == 0)
         {
-            var rect = _rectangles [i];
-            _rectangles [i] = new Rectangle (rect.Left + offsetX, rect.Top + offsetY, rect.Width, rect.Height);
+            return [];
         }
+
+        // Sweep-line algorithm to merge rectangles
+        List<(int x, bool isStart, int yTop, int yBottom)> events = new (rectangles.Count * 2); // Pre-allocate
+
+        foreach (Rectangle r in rectangles)
+        {
+            if (!r.IsEmpty)
+            {
+                events.Add ((r.Left, true, r.Top, r.Bottom)); // Start event
+                events.Add ((r.Right, false, r.Top, r.Bottom)); // End event
+            }
+        }
+
+        if (events.Count == 0)
+        {
+            return []; // Return empty list if no non-empty rectangles exist
+        }
+
+        events.Sort (
+                     (a, b) =>
+                     {
+                         int cmp = a.x.CompareTo (b.x);
+
+                         if (cmp != 0)
+                         {
+                             return cmp;
+                         }
+
+                         return a.isStart.CompareTo (b.isStart); // Start events before end events at same x
+                     });
+
+        List<Rectangle> merged = [];
+
+        SortedSet<(int yTop, int yBottom)> active = new (
+                                                         Comparer<(int yTop, int yBottom)>.Create (
+                                                                                                   (a, b) =>
+                                                                                                   {
+                                                                                                       int cmp = a.yTop.CompareTo (b.yTop);
+
+                                                                                                       return cmp != 0 ? cmp : a.yBottom.CompareTo (b.yBottom);
+                                                                                                   }));
+        int lastX = events [0].x;
+
+        foreach ((int x, bool isStart, int yTop, int yBottom) evt in events)
+        {
+            // Output rectangles for the previous segment if there are active rectangles
+            if (active.Count > 0 && evt.x > lastX)
+            {
+                merged.AddRange (MergeVerticalIntervals (active, lastX, evt.x));
+            }
+
+            // Process the event
+            if (evt.isStart)
+            {
+                active.Add ((evt.yTop, evt.yBottom));
+            }
+            else
+            {
+                active.Remove ((evt.yTop, evt.yBottom));
+            }
+
+            lastX = evt.x;
+        }
+
+        return minimize ? MinimizeRectangles (merged) : merged;
     }
 
     /// <summary>
-    ///     Merges overlapping rectangles into a minimal set of non-overlapping rectangles.
+    ///     Merges overlapping vertical intervals into a minimal set of non-overlapping rectangles.
     /// </summary>
-    /// <param name="rectangles">The list of rectangles to merge.</param>
+    /// <param name="active">The set of active vertical intervals.</param>
+    /// <param name="startX">The starting x-coordinate for the rectangles.</param>
+    /// <param name="endX">The ending x-coordinate for the rectangles.</param>
     /// <returns>A list of merged rectangles.</returns>
-    private List<Rectangle> MergeRectangles (List<Rectangle> rectangles)
+    internal static List<Rectangle> MergeVerticalIntervals (SortedSet<(int yTop, int yBottom)> active, int startX, int endX)
     {
-        // Simplified merging logic: this does not handle all edge cases for merging overlapping rectangles.
-        // For a full implementation, a plane sweep algorithm or similar would be needed.
-        List<Rectangle> merged = new List<Rectangle> (rectangles);
-        bool mergedAny;
+        if (active.Count == 0)
+        {
+            return [];
+        }
+
+        List<Rectangle> result = new (active.Count); // Pre-allocate
+        int? currentTop = null;
+        int? currentBottom = null;
+
+        foreach ((int yTop, int yBottom) in active)
+        {
+            if (currentTop == null)
+            {
+                currentTop = yTop;
+                currentBottom = yBottom;
+            }
+            else if (yTop <= currentBottom)
+            {
+                currentBottom = Math.Max (currentBottom.Value, yBottom);
+            }
+            else
+            {
+                result.Add (new (startX, currentTop.Value, endX - startX, currentBottom.Value - currentTop.Value));
+                currentTop = yTop;
+                currentBottom = yBottom;
+            }
+        }
+
+        if (currentTop != null)
+        {
+            result.Add (new (startX, currentTop.Value, endX - startX, currentBottom!.Value - currentTop.Value));
+        }
+
+        return result;
+    }
+
+    /// <summary>
+    ///     Minimizes a list of rectangles into the smallest possible set of non-overlapping rectangles
+    ///     by merging adjacent rectangles where possible.
+    /// </summary>
+    /// <param name="rectangles">The list of rectangles to minimize.</param>
+    /// <returns>A list of minimized rectangles.</returns>
+    internal static List<Rectangle> MinimizeRectangles (List<Rectangle> rectangles)
+    {
+        if (rectangles.Count <= 1)
+        {
+            return rectangles.ToList ();
+        }
+
+        List<Rectangle> minimized = new (rectangles.Count); // Pre-allocate
+        List<Rectangle> current = new (rectangles); // Work with a copy
+
+        bool changed;
 
         do
         {
-            mergedAny = false;
+            changed = false;
+            minimized.Clear ();
+
+            // Sort by Y then X for consistent processing
+            current.Sort (
+                          (a, b) =>
+                          {
+                              int cmp = a.Top.CompareTo (b.Top);
 
-            for (var i = 0; i < merged.Count; i++)
+                              return cmp != 0 ? cmp : a.Left.CompareTo (b.Left);
+                          });
+
+            var i = 0;
+
+            while (i < current.Count)
             {
-                for (int j = i + 1; j < merged.Count; j++)
+                Rectangle r = current [i];
+                int j = i + 1;
+
+                while (j < current.Count)
                 {
-                    if (merged [i].IntersectsWith (merged [j]))
+                    Rectangle next = current [j];
+
+                    // Check if rectangles can be merged horizontally (same Y range, adjacent X)
+                    if (r.Top == next.Top && r.Bottom == next.Bottom && (r.Right == next.Left || next.Right == r.Left || r.IntersectsWith (next)))
                     {
-                        merged [i] = Rectangle.Union (merged [i], merged [j]);
-                        merged.RemoveAt (j);
-                        mergedAny = true;
+                        r = new (
+                                 Math.Min (r.Left, next.Left),
+                                 r.Top,
+                                 Math.Max (r.Right, next.Right) - Math.Min (r.Left, next.Left),
+                                 r.Height
+                                );
+                        current.RemoveAt (j);
+                        changed = true;
+                    }
 
-                        break;
+                    // Check if rectangles can be merged vertically (same X range, adjacent Y)
+                    else if (r.Left == next.Left && r.Right == next.Right && (r.Bottom == next.Top || next.Bottom == r.Top || r.IntersectsWith (next)))
+                    {
+                        r = new (
+                                 r.Left,
+                                 Math.Min (r.Top, next.Top),
+                                 r.Width,
+                                 Math.Max (r.Bottom, next.Bottom) - Math.Min (r.Top, next.Top)
+                                );
+                        current.RemoveAt (j);
+                        changed = true;
+                    }
+                    else
+                    {
+                        j++;
                     }
                 }
 
-                if (mergedAny)
-                {
-                    break;
-                }
+                minimized.Add (r);
+                i++;
             }
+
+            current = minimized.ToList (); // Prepare for next iteration
         }
-        while (mergedAny);
+        while (changed);
 
-        return merged;
+        return minimized;
     }
 
     /// <summary>
@@ -290,8 +763,28 @@ public class Region : IDisposable
     /// <param name="original">The original rectangle.</param>
     /// <param name="subtract">The rectangle to subtract from the original.</param>
     /// <returns>An enumerable collection of resulting rectangles after subtraction.</returns>
-    private IEnumerable<Rectangle> SubtractRectangle (Rectangle original, Rectangle subtract)
+    internal static IEnumerable<Rectangle> SubtractRectangle (Rectangle original, Rectangle subtract)
     {
+        // Handle empty or invalid rectangles
+        if (original.IsEmpty || original.Width <= 0 || original.Height <= 0)
+        {
+            yield break; // Return empty enumeration for empty or invalid original
+        }
+
+        if (subtract.IsEmpty || subtract.Width <= 0 || subtract.Height <= 0)
+        {
+            yield return original;
+
+            yield break;
+        }
+
+        // Check for complete overlap (subtract fully contains or equals original)
+        if (subtract.Left <= original.Left && subtract.Top <= original.Top && subtract.Right >= original.Right && subtract.Bottom >= original.Bottom)
+        {
+            yield break; // Return empty if subtract completely overlaps original
+        }
+
+        // Check for no overlap
         if (!original.IntersectsWith (subtract))
         {
             yield return original;
@@ -299,19 +792,29 @@ public class Region : IDisposable
             yield break;
         }
 
-        // Top segment
+        // Fragment the original rectangle into segments excluding the subtract rectangle
+
+        // Top segment (above subtract)
         if (original.Top < subtract.Top)
         {
-            yield return new (original.Left, original.Top, original.Width, subtract.Top - original.Top);
+            yield return new (
+                              original.Left,
+                              original.Top,
+                              original.Width,
+                              subtract.Top - original.Top);
         }
 
-        // Bottom segment
+        // Bottom segment (below subtract)
         if (original.Bottom > subtract.Bottom)
         {
-            yield return new (original.Left, subtract.Bottom, original.Width, original.Bottom - subtract.Bottom);
+            yield return new (
+                              original.Left,
+                              subtract.Bottom,
+                              original.Width,
+                              original.Bottom - subtract.Bottom);
         }
 
-        // Left segment
+        // Left segment (to the left of subtract)
         if (original.Left < subtract.Left)
         {
             int top = Math.Max (original.Top, subtract.Top);
@@ -319,11 +822,15 @@ public class Region : IDisposable
 
             if (bottom > top)
             {
-                yield return new (original.Left, top, subtract.Left - original.Left, bottom - top);
+                yield return new (
+                                  original.Left,
+                                  top,
+                                  subtract.Left - original.Left,
+                                  bottom - top);
             }
         }
 
-        // Right segment
+        // Right segment (to the right of subtract)
         if (original.Right > subtract.Right)
         {
             int top = Math.Max (original.Top, subtract.Top);
@@ -331,13 +838,302 @@ public class Region : IDisposable
 
             if (bottom > top)
             {
-                yield return new (subtract.Right, top, original.Right - subtract.Right, bottom - top);
+                yield return new (
+                                  subtract.Right,
+                                  top,
+                                  original.Right - subtract.Right,
+                                  bottom - top);
             }
         }
     }
 
     /// <summary>
-    ///     Releases all resources used by the <see cref="Region"/>.
+    ///     Fills the interior of all rectangles in the region with the specified attribute and fill rune.
     /// </summary>
-    public void Dispose () { _rectangles.Clear (); }
+    /// <param name="attribute">The attribute (color/style) to use.</param>
+    /// <param name="fillRune">
+    ///     The rune to fill the interior of the rectangles with. If <cref langword="null"/> space will be
+    ///     used.
+    /// </param>
+    public void FillRectangles (Attribute attribute, Rune? fillRune = null)
+    {
+        if (_rectangles.Count == 0)
+        {
+            return;
+        }
+
+        foreach (Rectangle rect in _rectangles)
+        {
+            if (rect.IsEmpty || rect.Width <= 0 || rect.Height <= 0)
+            {
+                continue;
+            }
+
+            Application.Driver?.SetAttribute (attribute);
+
+            for (int y = rect.Top; y < rect.Bottom; y++)
+            {
+                for (int x = rect.Left; x < rect.Right; x++)
+                {
+                    Application.Driver?.Move (x, y);
+                    Application.Driver?.AddRune (fillRune ?? (Rune)' ');
+                }
+            }
+        }
+    }
+
+
+    /// <summary>
+    ///     Draws the boundaries of all rectangles in the region using the specified attributes, only if the rectangle is big
+    ///     enough.
+    /// </summary>
+    /// <param name="canvas">The canvas to draw on.</param>
+    /// <param name="style">The line style to use for drawing.</param>
+    /// <param name="attribute">The attribute (color/style) to use for the lines. If <c>null</c>.</param>
+    public void DrawBoundaries (LineCanvas canvas, LineStyle style, Attribute? attribute = null)
+    {
+        if (_rectangles.Count == 0)
+        {
+            return;
+        }
+
+        foreach (Rectangle rect in _rectangles)
+        {
+            if (rect.IsEmpty || rect.Width <= 0 || rect.Height <= 0)
+            {
+                continue;
+            }
+
+            // Only draw boundaries if the rectangle is "big enough" (e.g., width and height > 1)
+            //if (rect.Width > 2 && rect.Height > 2)
+            {
+                if (rect.Width > 1)
+                {
+                    // Add horizontal lines
+                    canvas.AddLine (new (rect.Left, rect.Top), rect.Width, Orientation.Horizontal, style, attribute);
+                    canvas.AddLine (new (rect.Left, rect.Bottom - 1), rect.Width, Orientation.Horizontal, style, attribute);
+                }
+
+                if (rect.Height > 1)
+                {
+                    // Add vertical lines 
+                    canvas.AddLine (new (rect.Left, rect.Top), rect.Height, Orientation.Vertical, style, attribute);
+                    canvas.AddLine (new (rect.Right - 1, rect.Top), rect.Height, Orientation.Vertical, style, attribute);
+                }
+            }
+        }
+    }
+
+
+    // BUGBUG: DrawOuterBoundary does not work right. it draws all regions +1 too tall/wide. It should draw single width/height regions as just a line.
+    //
+    // Example: There are 3 regions here. the first is a rect (0,0,1,4). Second is (10, 0, 2, 4). 
+    // This is how they should draw:
+    //
+    // |123456789|123456789|123456789
+    // 1 │        ┌┐        ┌─┐ 
+    // 2 │        ││        │ │ 
+    // 3 │        ││        │ │ 
+    // 4 │        └┘        └─┘
+    // 
+    // But this is what it draws:
+    // |123456789|123456789|123456789
+    // 1┌┐        ┌─┐       ┌──┐     
+    // 2││        │ │       │  │     
+    // 3││        │ │       │  │     
+    // 4││        │ │       │  │     
+    // 5└┘        └─┘       └──┘         
+    //
+    // Example: There are two rectangles in this region. (0,0,3,3) and (3, 3, 3, 3).
+    // This is fill - correct:
+    // |123456789
+    // 1░░░      
+    // 2░░░      
+    // 3░░░░░    
+    // 4  ░░░    
+    // 5  ░░░    
+    // 6         
+    //
+    // This is what DrawOuterBoundary should draw
+    // |123456789|123456789
+    // 1┌─┐               
+    // 2│ │             
+    // 3└─┼─┐             
+    // 4  │ │             
+    // 5  └─┘             
+    // 6
+    //
+    // This is what DrawOuterBoundary actually draws
+    // |123456789|123456789
+    // 1┌──┐               
+    // 2│  │               
+    // 3│  └─┐             
+    // 4└─┐  │             
+    // 5  │  │             
+    // 6  └──┘             
+
+    /// <summary>
+    ///     Draws the outer perimeter of the region to <paramref name="lineCanvas"/> using <paramref name="style"/> and
+    ///     <paramref name="attribute"/>.
+    ///     The outer perimeter follows the shape of the rectangles in the region, even if non-rectangular, by drawing
+    ///     boundaries and excluding internal lines.
+    /// </summary>
+    /// <param name="lineCanvas">The LineCanvas to draw on.</param>
+    /// <param name="style">The line style to use for drawing.</param>
+    /// <param name="attribute">The attribute (color/style) to use for the lines. If <c>null</c>.</param>
+    public void DrawOuterBoundary (LineCanvas lineCanvas, LineStyle style, Attribute? attribute = null)
+    {
+        if (_rectangles.Count == 0)
+        {
+            return;
+        }
+
+        // Get the bounds of the region
+        Rectangle bounds = GetBounds ();
+
+        // Add protection against extremely large allocations
+        if (bounds.Width > 1000 || bounds.Height > 1000)
+        {
+            // Fall back to drawing each rectangle's boundary
+            DrawBoundaries(lineCanvas, style, attribute);
+            return;
+        }
+
+        // Create a grid to track which cells are inside the region
+        var insideRegion = new bool [bounds.Width + 1, bounds.Height + 1];
+
+        // Fill the grid based on rectangles
+        foreach (Rectangle rect in _rectangles)
+        {
+            if (rect.IsEmpty || rect.Width <= 0 || rect.Height <= 0)
+            {
+                continue;
+            }
+
+            for (int x = rect.Left; x < rect.Right; x++)
+            {
+                for (int y = rect.Top; y < rect.Bottom; y++)
+                {
+                    // Adjust coordinates to grid space
+                    int gridX = x - bounds.Left;
+                    int gridY = y - bounds.Top;
+
+                    if (gridX >= 0 && gridX < bounds.Width && gridY >= 0 && gridY < bounds.Height)
+                    {
+                        insideRegion [gridX, gridY] = true;
+                    }
+                }
+            }
+        }
+
+        // Find horizontal boundary lines
+        for (var y = 0; y <= bounds.Height; y++)
+        {
+            int startX = -1;
+
+            for (var x = 0; x <= bounds.Width; x++)
+            {
+                bool above = y > 0 && insideRegion [x, y - 1];
+                bool below = y < bounds.Height && insideRegion [x, y];
+
+                // A boundary exists where one side is inside and the other is outside
+                bool isBoundary = above != below;
+
+                if (isBoundary)
+                {
+                    // Start a new segment or continue the current one
+                    if (startX == -1)
+                    {
+                        startX = x;
+                    }
+                }
+                else
+                {
+                    // End the current segment if one exists
+                    if (startX != -1)
+                    {
+                        int length = x - startX + 1; // Add 1 to make sure lines connect
+
+                        lineCanvas.AddLine (
+                                            new (startX + bounds.Left, y + bounds.Top),
+                                            length,
+                                            Orientation.Horizontal,
+                                            style,
+                                            attribute
+                                           );
+                        startX = -1;
+                    }
+                }
+            }
+
+            // End any segment that reaches the right edge
+            if (startX != -1)
+            {
+                int length = bounds.Width + 1 - startX + 1; // Add 1 to make sure lines connect
+
+                lineCanvas.AddLine (
+                                    new (startX + bounds.Left, y + bounds.Top),
+                                    length,
+                                    Orientation.Horizontal,
+                                    style,
+                                    attribute
+                                   );
+            }
+        }
+
+        // Find vertical boundary lines
+        for (var x = 0; x <= bounds.Width; x++)
+        {
+            int startY = -1;
+
+            for (var y = 0; y <= bounds.Height; y++)
+            {
+                bool left = x > 0 && insideRegion [x - 1, y];
+                bool right = x < bounds.Width && insideRegion [x, y];
+
+                // A boundary exists where one side is inside and the other is outside
+                bool isBoundary = left != right;
+
+                if (isBoundary)
+                {
+                    // Start a new segment or continue the current one
+                    if (startY == -1)
+                    {
+                        startY = y;
+                    }
+                }
+                else
+                {
+                    // End the current segment if one exists
+                    if (startY != -1)
+                    {
+                        int length = y - startY + 1; // Add 1 to make sure lines connect
+
+                        lineCanvas.AddLine (
+                                            new (x + bounds.Left, startY + bounds.Top),
+                                            length,
+                                            Orientation.Vertical,
+                                            style,
+                                            attribute
+                                           );
+                        startY = -1;
+                    }
+                }
+            }
+
+            // End any segment that reaches the bottom edge
+            if (startY != -1)
+            {
+                int length = bounds.Height + 1 - startY + 1; // Add 1 to make sure lines connect
+
+                lineCanvas.AddLine (
+                                    new (x + bounds.Left, startY + bounds.Top),
+                                    length,
+                                    Orientation.Vertical,
+                                    style,
+                                    attribute
+                                   );
+            }
+        }
+    }
 }

+ 128 - 0
Terminal.Gui/Drawing/RegionOp.cs

@@ -0,0 +1,128 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Specifies the operation to perform when combining two regions or a <see cref="Region"/> with a
+///     <see cref="Rectangle"/>>, defining how their
+///     rectangular areas are merged, intersected, or subtracted.
+/// </summary>
+/// <remarks>
+///     <para>
+///         Each operation modifies the first region's set of rectangles based on the second (op) region or rectangle,
+///         producing a new set of non-overlapping rectangles. The operations align with set theory, enabling flexible
+///         manipulation for TUI layout, clipping, or drawing. Developers can choose between granular outputs (e.g.,
+///         <see cref="Union"/>) that preserve detailed rectangles or minimal outputs (e.g., <see cref="MinimalUnion"/>)
+///         that reduce the rectangle count for compactness.
+///     </para>
+/// </remarks>
+public enum RegionOp
+{
+    /// <summary>
+    ///     Subtracts the second (op) region or rectangle from the first region, removing any areas where the op overlaps
+    ///     the first region. The result includes only the portions of the first region that do not intersect with the op.
+    ///     <para>
+    ///         For example, if the first region contains rectangle A = (0,0,10,10) and the op is B = (5,5,5,5), the result
+    ///         would include rectangles covering A minus the overlapping part of B, such as (0,0,10,5), (0,5,5,5), and
+    ///         (5,10,5,5).
+    ///     </para>
+    ///     <para>
+    ///         If the op region is empty or null, the operation has no effect unless the first region is also empty, in
+    ///         which case it clears the first region.
+    ///     </para>
+    /// </summary>
+    Difference = 0,
+
+    /// <summary>
+    ///     Intersects the first region with the second (op) region or rectangle, retaining only the areas where both
+    ///     regions overlap. The result includes rectangles covering the common areas, excluding any parts unique to either
+    ///     region.
+    ///     <para>
+    ///         For example, if the first region contains A = (0,0,10,10) and the op is B = (5,5,5,5), the result would be
+    ///         a single rectangle (5,5,5,5), representing the intersection.
+    ///     </para>
+    ///     <para>
+    ///         If either region is empty or null, the result clears the first region, as there’s no intersection possible.
+    ///     </para>
+    /// </summary>
+    Intersect = 1,
+
+    /// <summary>
+    ///     Performs a union (inclusive-or) of the first region and the second (op) region or rectangle, combining all
+    ///     areas covered by either region into a single contiguous region without holes (unless explicitly subtracted).
+    ///     <para>
+    ///         The formal union (∪) includes all points in at least one rectangle, producing a granular set of
+    ///         non-overlapping rectangles that cover the combined area. For example, if the first region contains A =
+    ///         (0,0,5,5) and the op is B = (5,0,5,5), the result might include (0,0,5,5) and (5,0,5,5) unless minimized.
+    ///     </para>
+    ///     <para>
+    ///         This operation uses granular output (preserving detailed rectangles). To minimize the result use
+    ///         <see cref="MinimalUnion"/> instead.
+    ///     </para>
+    ///     <para>
+    ///         If the op region is empty or null, the first region remains unchanged.
+    ///     </para>
+    /// </summary>
+    Union = 2,
+
+    /// <summary>
+    ///     Performs a minimal union (inclusive-or) of the first region and the second (op) region or rectangle, merging adjacent or
+    ///     overlapping rectangles into the smallest possible set of non-overlapping rectangles that cover the combined
+    ///     area.
+    ///     <para>
+    ///         This operation minimizes the number of rectangles, producing a more compact representation compared to
+    ///         <see cref="Union"/>. For example, if the first region contains A = (0,0,5,5) and the op is B = (5,0,5,5),
+    ///         the result would be a single rectangle (0,0,10,5), reducing redundancy.
+    ///     </para>
+    ///     <para>
+    ///         This operation always minimizes the output and has lower performance than <see cref="Union"/>.
+    ///     </para>
+    ///     <para>
+    ///         If the op region is empty or null, the first region remains unchanged.
+    ///     </para>
+    /// </summary>
+    MinimalUnion = 3,
+
+    /// <summary>
+    ///     Performs an exclusive-or (XOR) of the first region and the second (op) region or rectangle, retaining only the
+    ///     areas that are unique to each region—i.e., areas present in one region but not both.
+    ///     <para>
+    ///         For example, if the first region contains A = (0,0,10,10) and the op is B = (5,5,5,5), the result would
+    ///         include rectangles covering (0,0,10,5), (0,5,5,5), (5,10,5,5), and (5,5,5,5), excluding the intersection
+    ///         (5,5,5,5).
+    ///     </para>
+    ///     <para>
+    ///         If the op region is empty or null, this operation excludes the first region from itself (clearing it) or
+    ///         adds the first region to the op (if op is empty), depending on the logic.
+    ///     </para>
+    /// </summary>
+    XOR = 4,
+
+    /// <summary>
+    ///     Subtracts the first region from the second (op) region or rectangle, retaining only the areas of the op that do
+    ///     not overlap with the first region. The result replaces the first region with these areas.
+    ///     <para>
+    ///         For example, if the first region contains A = (5,5,5,5) and the op is B = (0,0,10,10), the result would
+    ///         include rectangles covering B minus A, such as (0,0,10,5), (0,5,5,5), and (5,10,5,5).
+    ///     </para>
+    ///     <para>
+    ///         If the first region is empty or null, the op region replaces the first region. If the op region is empty,
+    ///         the first region is cleared.
+    ///     </para>
+    /// </summary>
+    ReverseDifference = 5,
+
+    /// <summary>
+    ///     Replaces the first region entirely with the second (op) region or rectangle, discarding the first region's
+    ///     current rectangles and adopting the op's rectangles.
+    ///     <para>
+    ///         For example, if the first region contains (0,0,5,5) and the op is (10,10,5,5), the first region will be
+    ///         cleared and replaced with (10,10,5,5).
+    ///     </para>
+    ///     <para>
+    ///         If the op region is empty or null, the first region is cleared. This operation is useful for resetting or
+    ///         overwriting region state.
+    ///     </para>
+    /// </summary>
+    Replace = 6
+}

+ 13 - 0
Terminal.Gui/Drawing/Thickness.cs

@@ -235,6 +235,19 @@ public record struct Thickness
         return new (x, y, width, height);
     }
 
+    /// <summary>
+    ///     Returns a region describing the thickness.
+    /// </summary>
+    /// <param name="rect">The source rectangle</param>
+    /// <returns></returns>
+    public Region AsRegion (Rectangle rect)
+    {
+        Region region = new Region (rect);
+        region.Exclude (GetInside (rect));
+
+        return region;
+    }
+
     /// <summary>
     ///     Gets the total width of the left and right sides of the rectangle. Sets the width of the left and right sides
     ///     of the rectangle to half the specified value.

+ 271 - 1
Terminal.Gui/Text/TextFormatter.cs

@@ -438,7 +438,7 @@ public class TextFormatter
             }
         }
     }
-
+    
     /// <summary>
     ///     Determines if the viewport width will be used or only the text width will be used,
     ///     If <see langword="true"/> all the viewport area will be filled with whitespaces and the same background color
@@ -864,6 +864,276 @@ public class TextFormatter
         return value;
     }
 
+    /// <summary>
+    ///     Calculates and returns a <see cref="Region"/> describing the areas where text would be output, based on the
+    ///     formatting rules of <see cref="TextFormatter"/>.
+    /// </summary>
+    /// <remarks>
+    ///     Uses the same formatting logic as <see cref="Draw"/>, including alignment, direction, word wrap, and constraints,
+    ///     but does not perform actual drawing to <see cref="IConsoleDriver"/>.
+    /// </remarks>
+    /// <param name="screen">Specifies the screen-relative location and maximum size for drawing the text.</param>
+    /// <param name="maximum">Specifies the screen-relative location and maximum container size.</param>
+    /// <returns>A <see cref="Region"/> representing the areas where text would be drawn.</returns>
+    public Region GetDrawRegion (Rectangle screen, Rectangle maximum = default)
+    {
+        Region drawnRegion = new Region ();
+
+        // With this check, we protect against subclasses with overrides of Text (like Button)
+        if (string.IsNullOrEmpty (Text))
+        {
+            return drawnRegion;
+        }
+
+        List<string> linesFormatted = GetLines ();
+
+        bool isVertical = IsVerticalDirection (Direction);
+        Rectangle maxScreen = screen;
+
+        // INTENT: What, exactly, is the intent of this?
+        maxScreen = maximum == default (Rectangle)
+                        ? screen
+                        : new (
+                               Math.Max (maximum.X, screen.X),
+                               Math.Max (maximum.Y, screen.Y),
+                               Math.Max (
+                                         Math.Min (maximum.Width, maximum.Right - screen.Left),
+                                         0
+                                        ),
+                               Math.Max (
+                                         Math.Min (
+                                                   maximum.Height,
+                                                   maximum.Bottom - screen.Top
+                                                  ),
+                                         0
+                                        )
+                              );
+
+        if (maxScreen.Width == 0 || maxScreen.Height == 0)
+        {
+            return drawnRegion;
+        }
+
+        int lineOffset = !isVertical && screen.Y < 0 ? Math.Abs (screen.Y) : 0;
+
+        for (int line = lineOffset; line < linesFormatted.Count; line++)
+        {
+            if ((isVertical && line > screen.Width) || (!isVertical && line > screen.Height))
+            {
+                continue;
+            }
+
+            if ((isVertical && line >= maxScreen.Left + maxScreen.Width)
+                || (!isVertical && line >= maxScreen.Top + maxScreen.Height + lineOffset))
+            {
+                break;
+            }
+
+            Rune [] runes = linesFormatted [line].ToRunes ();
+
+            // When text is justified, we lost left or right, so we use the direction to align. 
+            int x = 0, y = 0;
+
+            switch (Alignment)
+            {
+                // Horizontal Alignment
+                case Alignment.End when isVertical:
+                {
+                    int runesWidth = GetColumnsRequiredForVerticalText (linesFormatted, line, linesFormatted.Count - line, TabWidth);
+                    x = screen.Right - runesWidth;
+
+                    break;
+                }
+                case Alignment.End:
+                {
+                    int runesWidth = StringExtensions.ToString (runes).GetColumns ();
+                    x = screen.Right - runesWidth;
+
+                    break;
+                }
+                case Alignment.Start when isVertical:
+                {
+                    int runesWidth = line > 0
+                                         ? GetColumnsRequiredForVerticalText (linesFormatted, 0, line, TabWidth)
+                                         : 0;
+                    x = screen.Left + runesWidth;
+
+                    break;
+                }
+                case Alignment.Start:
+                    x = screen.Left;
+
+                    break;
+                case Alignment.Fill when isVertical:
+                {
+                    int runesWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, linesFormatted.Count, TabWidth);
+                    int prevLineWidth = line > 0 ? GetColumnsRequiredForVerticalText (linesFormatted, line - 1, 1, TabWidth) : 0;
+                    int firstLineWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, 1, TabWidth);
+                    int lastLineWidth = GetColumnsRequiredForVerticalText (linesFormatted, linesFormatted.Count - 1, 1, TabWidth);
+                    var interval = (int)Math.Round ((double)(screen.Width + firstLineWidth + lastLineWidth) / linesFormatted.Count);
+
+                    x = line == 0
+                            ? screen.Left
+                            : line < linesFormatted.Count - 1
+                                ? screen.Width - runesWidth <= lastLineWidth ? screen.Left + prevLineWidth : screen.Left + line * interval
+                                : screen.Right - lastLineWidth;
+
+                    break;
+                }
+                case Alignment.Fill:
+                    x = screen.Left;
+
+                    break;
+                case Alignment.Center when isVertical:
+                {
+                    int runesWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, linesFormatted.Count, TabWidth);
+                    int linesWidth = GetColumnsRequiredForVerticalText (linesFormatted, 0, line, TabWidth);
+                    x = screen.Left + linesWidth + (screen.Width - runesWidth) / 2;
+
+                    break;
+                }
+                case Alignment.Center:
+                {
+                    int runesWidth = StringExtensions.ToString (runes).GetColumns ();
+                    x = screen.Left + (screen.Width - runesWidth) / 2;
+
+                    break;
+                }
+                default:
+                    Debug.WriteLine ($"Unsupported Alignment: {nameof (VerticalAlignment)}");
+
+                    return drawnRegion;
+            }
+
+            switch (VerticalAlignment)
+            {
+                // Vertical Alignment
+                case Alignment.End when isVertical:
+                    y = screen.Bottom - runes.Length;
+
+                    break;
+                case Alignment.End:
+                    y = screen.Bottom - linesFormatted.Count + line;
+
+                    break;
+                case Alignment.Start when isVertical:
+                    y = screen.Top;
+
+                    break;
+                case Alignment.Start:
+                    y = screen.Top + line;
+
+                    break;
+                case Alignment.Fill when isVertical:
+                    y = screen.Top;
+
+                    break;
+                case Alignment.Fill:
+                {
+                    var interval = (int)Math.Round ((double)(screen.Height + 2) / linesFormatted.Count);
+
+                    y = line == 0 ? screen.Top :
+                        line < linesFormatted.Count - 1 ? screen.Height - interval <= 1 ? screen.Top + 1 : screen.Top + line * interval : screen.Bottom - 1;
+
+                    break;
+                }
+                case Alignment.Center when isVertical:
+                {
+                    int s = (screen.Height - runes.Length) / 2;
+                    y = screen.Top + s;
+
+                    break;
+                }
+                case Alignment.Center:
+                {
+                    int s = (screen.Height - linesFormatted.Count) / 2;
+                    y = screen.Top + line + s;
+
+                    break;
+                }
+                default:
+                    Debug.WriteLine ($"Unsupported Alignment: {nameof (VerticalAlignment)}");
+
+                    return drawnRegion;
+            }
+
+            int colOffset = screen.X < 0 ? Math.Abs (screen.X) : 0;
+            int start = isVertical ? screen.Top : screen.Left;
+            int size = isVertical ? screen.Height : screen.Width;
+            int current = start + colOffset;
+            int zeroLengthCount = isVertical ? runes.Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0;
+
+            int lineX = x, lineY = y, lineWidth = 0, lineHeight = 1;
+
+            for (int idx = (isVertical ? start - y : start - x) + colOffset;
+                 current < start + size + zeroLengthCount;
+                 idx++)
+            {
+                if (idx < 0
+                    || (isVertical
+                            ? VerticalAlignment != Alignment.End && current < 0
+                            : Alignment != Alignment.End && x + current + colOffset < 0))
+                {
+                    current++;
+
+                    continue;
+                }
+
+                if (!FillRemaining && idx > runes.Length - 1)
+                {
+                    break;
+                }
+
+                if ((!isVertical
+                     && (current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset
+                         || (idx < runes.Length && runes [idx].GetColumns () > screen.Width)))
+                    || (isVertical
+                        && ((current > start + size + zeroLengthCount && idx > maxScreen.Top + maxScreen.Height - screen.Y)
+                            || (idx < runes.Length && runes [idx].GetColumns () > screen.Width))))
+                {
+                    break;
+                }
+
+                Rune rune = idx >= 0 && idx < runes.Length ? runes [idx] : (Rune)' ';
+                int runeWidth = GetRuneWidth (rune, TabWidth);
+
+                if (isVertical)
+                {
+                    if (runeWidth > 0)
+                    {
+                        // Update line height for vertical text (each rune is a column)
+                        lineHeight = Math.Max (lineHeight, current - y + 1);
+                        lineWidth = Math.Max (lineWidth, 1); // Width is 1 per rune in vertical
+                    }
+                }
+                else
+                {
+                    // Update line width and position for horizontal text
+                    lineWidth += runeWidth;
+                }
+
+                current += isVertical && runeWidth > 0 ? 1 : runeWidth;
+
+                int nextRuneWidth = idx + 1 > -1 && idx + 1 < runes.Length
+                                        ? runes [idx + 1].GetColumns ()
+                                        : 0;
+
+                if (!isVertical && idx + 1 < runes.Length && current + nextRuneWidth > start + size)
+                {
+                    break;
+                }
+            }
+
+            // Add the line's drawn region to the overall region
+            if (lineWidth > 0 && lineHeight > 0)
+            {
+                drawnRegion.Union (new Rectangle (lineX, lineY, lineWidth, lineHeight));
+            }
+        }
+
+        return drawnRegion;
+    }
+
     #region Static Members
 
     /// <summary>Check if it is a horizontal direction</summary>

+ 1 - 3
Terminal.Gui/View/Adornment/Adornment.cs

@@ -182,6 +182,7 @@ public class Adornment : View, IDesignable
     /// <inheritdoc/>
     protected override bool OnDrawingSubviews () { return Thickness == Thickness.Empty; }
 
+
     /// <summary>Does nothing for Adornment</summary>
     /// <returns></returns>
     protected override bool OnRenderingLineCanvas () { return true; }
@@ -196,9 +197,6 @@ public class Adornment : View, IDesignable
         set => throw new InvalidOperationException (@"Adornment can only render to their Parent or Parent's Superview.");
     }
 
-    /// <inheritdoc/>
-    protected override void OnDrawComplete () { }
-
     /// <summary>
     ///     Indicates whether the specified Parent's SuperView-relative coordinates are within the Adornment's Thickness.
     /// </summary>

+ 20 - 20
Terminal.Gui/View/Adornment/Border.cs

@@ -313,7 +313,7 @@ public class Border : Adornment
             // Adornment.Contains takes Parent SuperView=relative coords.
             if (Contains (new (mouseEvent.Position.X + Parent.Frame.X + Frame.X, mouseEvent.Position.Y + Parent.Frame.Y + Frame.Y)))
             {
-                if (_arranging != ViewArrangement.Fixed)
+                if (Arranging != ViewArrangement.Fixed)
                 {
                     EndArrangeMode ();
                 }
@@ -480,7 +480,7 @@ public class Border : Adornment
                 int minWidth = Thickness.Horizontal + Parent!.Margin!.Thickness.Right;
 
                 // TODO: This code can be refactored to be more readable and maintainable.
-                switch (_arranging)
+                switch (Arranging)
                 {
                     case ViewArrangement.Movable:
 
@@ -979,7 +979,7 @@ public class Border : Adornment
         steps = [15];
     }
 
-    private ViewArrangement _arranging;
+    internal ViewArrangement Arranging { get; set; }
 
     private Button? _moveButton; // always top-left
     private Button? _allSizeButton;
@@ -1000,7 +1000,7 @@ public class Border : Adornment
     /// <returns></returns>
     public bool? EnterArrangeMode (ViewArrangement arrangement)
     {
-        Debug.Assert (_arranging == ViewArrangement.Fixed);
+        Debug.Assert (Arranging == ViewArrangement.Fixed);
 
         if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable)
             && !Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable)
@@ -1163,16 +1163,16 @@ public class Border : Adornment
                 _allSizeButton!.Visible = true;
             }
 
-            _arranging = ViewArrangement.Movable;
+            Arranging = ViewArrangement.Movable;
             CanFocus = true;
             SetFocus ();
         }
         else
         {
             // Mouse mode
-            _arranging = arrangement;
+            Arranging = arrangement;
 
-            switch (_arranging)
+            switch (Arranging)
             {
                 case ViewArrangement.Movable:
                     _moveButton!.Visible = true;
@@ -1247,13 +1247,13 @@ public class Border : Adornment
             }
         }
 
-        if (_arranging != ViewArrangement.Fixed)
+        if (Arranging != ViewArrangement.Fixed)
         {
             if (arrangement == ViewArrangement.Fixed)
             {
                 // Keyboard mode - enable nav
                 // TODO: Keyboard mode only supports sizing from bottom/right.
-                _arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
+                Arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
             }
 
             return true;
@@ -1278,12 +1278,12 @@ public class Border : Adornment
                             return false;
                         }
 
-                        if (_arranging == ViewArrangement.Movable)
+                        if (Arranging == ViewArrangement.Movable)
                         {
                             Parent!.Y = Parent.Y - 1;
                         }
 
-                        if (_arranging == ViewArrangement.Resizable)
+                        if (Arranging == ViewArrangement.Resizable)
                         {
                             if (Parent!.Viewport.Height > 0)
                             {
@@ -1303,12 +1303,12 @@ public class Border : Adornment
                             return false;
                         }
 
-                        if (_arranging == ViewArrangement.Movable)
+                        if (Arranging == ViewArrangement.Movable)
                         {
                             Parent!.Y = Parent.Y + 1;
                         }
 
-                        if (_arranging == ViewArrangement.Resizable)
+                        if (Arranging == ViewArrangement.Resizable)
                         {
                             Parent!.Height = Parent.Height! + 1;
                         }
@@ -1325,12 +1325,12 @@ public class Border : Adornment
                             return false;
                         }
 
-                        if (_arranging == ViewArrangement.Movable)
+                        if (Arranging == ViewArrangement.Movable)
                         {
                             Parent!.X = Parent.X - 1;
                         }
 
-                        if (_arranging == ViewArrangement.Resizable)
+                        if (Arranging == ViewArrangement.Resizable)
                         {
                             if (Parent!.Viewport.Width > 0)
                             {
@@ -1350,12 +1350,12 @@ public class Border : Adornment
                             return false;
                         }
 
-                        if (_arranging == ViewArrangement.Movable)
+                        if (Arranging == ViewArrangement.Movable)
                         {
                             Parent!.X = Parent.X + 1;
                         }
 
-                        if (_arranging == ViewArrangement.Resizable)
+                        if (Arranging == ViewArrangement.Resizable)
                         {
                             Parent!.Width = Parent.Width! + 1;
                         }
@@ -1373,7 +1373,7 @@ public class Border : Adornment
                         // BUGBUG: the view hierachy.
 
                         AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
-                        _arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
+                        Arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
 
                         return true; // Always eat
                     });
@@ -1383,7 +1383,7 @@ public class Border : Adornment
                     () =>
                     {
                         AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop);
-                        _arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
+                        Arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
 
                         return true; // Always eat
                     });
@@ -1419,7 +1419,7 @@ public class Border : Adornment
     private bool? EndArrangeMode ()
     {
         // Debug.Assert (_arranging != ViewArrangement.Fixed);
-        _arranging = ViewArrangement.Fixed;
+        Arranging = ViewArrangement.Fixed;
 
         Application.MouseEvent -= ApplicationOnMouseEvent;
 

+ 54 - 0
Terminal.Gui/View/DrawContext.cs

@@ -0,0 +1,54 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>
+///     Tracks the region that has been drawn during <see cref="View.Draw"/>. This is primarily
+///     in support of <see cref="ViewportSettings.Transparent"/>.
+/// </summary>
+public class DrawContext
+{
+    private readonly Region _drawnRegion = new Region ();
+
+    /// <summary>
+    /// Gets a copy of the region drawn so far in this context.
+    /// </summary>
+    public Region GetDrawnRegion () => _drawnRegion.Clone ();
+
+    /// <summary>
+    /// Reports that a rectangle has been drawn.
+    /// </summary>
+    /// <param name="rect">The rectangle that was drawn.</param>
+    public void AddDrawnRectangle (Rectangle rect)
+    {
+        _drawnRegion.Combine (rect, RegionOp.Union);
+    }
+
+    /// <summary>
+    /// Reports that a region has been drawn.
+    /// </summary>
+    /// <param name="region">The region that was drawn.</param>
+    public void AddDrawnRegion (Region region)
+    {
+        _drawnRegion.Combine (region, RegionOp.Union);
+    }
+
+    /// <summary>
+    /// Clips (intersects) the drawn region with the specified rectangle.
+    /// This modifies the internal drawn region directly.
+    /// </summary>
+    /// <param name="clipRect">The clipping rectangle.</param>
+    public void ClipDrawnRegion (Rectangle clipRect)
+    {
+        _drawnRegion.Intersect (clipRect);
+    }
+
+    /// <summary>
+    /// Clips (intersects) the drawn region with the specified region.
+    /// This modifies the internal drawn region directly.
+    /// </summary>
+    /// <param name="clipRegion">The clipping region.</param>
+    public void ClipDrawnRegion (Region clipRegion)
+    {
+        _drawnRegion.Intersect (clipRegion);
+    }
+}

+ 16 - 2
Terminal.Gui/View/DrawEventArgs.cs

@@ -1,4 +1,5 @@
-using System.ComponentModel;
+#nullable enable
+using System.ComponentModel;
 
 namespace Terminal.Gui;
 
@@ -14,10 +15,16 @@ public class DrawEventArgs : CancelEventArgs
     ///     The Content-relative rectangle describing the old visible viewport into the
     ///     <see cref="View"/>.
     /// </param>
-    public DrawEventArgs (Rectangle newViewport, Rectangle oldViewport)
+    /// <param name="drawContext">
+    ///     Add any regions that have been drawn to during <see cref="View.Draw"/> operations to this context. This is
+    ///     primarily
+    ///     in support of <see cref="ViewportSettings.Transparent"/>.
+    /// </param>
+    public DrawEventArgs (Rectangle newViewport, Rectangle oldViewport, DrawContext? drawContext)
     {
         NewViewport = newViewport;
         OldViewport = oldViewport;
+        DrawContext = drawContext;
     }
 
     /// <summary>Gets the Content-relative rectangle describing the old visible viewport into the <see cref="View"/>.</summary>
@@ -25,4 +32,11 @@ public class DrawEventArgs : CancelEventArgs
 
     /// <summary>Gets the Content-relative rectangle describing the currently visible viewport into the <see cref="View"/>.</summary>
     public Rectangle NewViewport { get; }
+
+    /// <summary>
+    ///     Add any regions that have been drawn to during <see cref="View.Draw"/> operations to this context. This is
+    ///     primarily
+    ///     in support of <see cref="ViewportSettings.Transparent"/>.
+    /// </summary>
+    public DrawContext? DrawContext { get; }
 }

+ 15 - 1
Terminal.Gui/View/View.Content.cs

@@ -385,7 +385,7 @@ public partial class View
 
     private void RaiseViewportChangedEvent (Rectangle oldViewport)
     {
-        var args = new DrawEventArgs (IsInitialized ? Viewport : Rectangle.Empty, oldViewport);
+        var args = new DrawEventArgs (IsInitialized ? Viewport : Rectangle.Empty, oldViewport, null);
         OnViewportChanged (args);
         ViewportChanged?.Invoke (this, args);
     }
@@ -430,6 +430,20 @@ public partial class View
         return screen.Location;
     }
 
+    /// <summary>
+    ///     Gets the Viewport rectangle with a screen-relative location.
+    /// </summary>
+    /// <returns>Screen-relative location and size.</returns>
+    public Rectangle ViewportToScreen ()
+    {
+        // Translate bounds to Frame (our SuperView's Viewport-relative coordinates)
+        Rectangle screen = FrameToScreen ();
+        Point viewportOffset = GetViewportOffsetFromFrame ();
+        screen.Offset (viewportOffset.X, viewportOffset.Y);
+
+        return screen;
+    }
+
     /// <summary>Converts a screen-relative coordinate to a Viewport-relative coordinate.</summary>
     /// <returns>The coordinate relative to this view's <see cref="Viewport"/>.</returns>
     /// <remarks>

+ 14 - 4
Terminal.Gui/View/View.Drawing.Clipping.cs

@@ -11,8 +11,7 @@ public partial class View
     ///         There is a single clip region for the entire application.
     ///     </para>
     ///     <para>
-    ///         This method returns the current clip region, not a clone. If there is a need to modify the clip region, it is
-    ///         recommended to clone it first.
+    ///         This method returns the current clip region, not a clone. If there is a need to modify the clip region, clone it first.
     ///     </para>
     /// </remarks>
     /// <returns>The current Clip.</returns>
@@ -74,6 +73,17 @@ public partial class View
     /// <param name="rectangle"></param>
     public static void ExcludeFromClip (Rectangle rectangle) { Driver?.Clip?.Exclude (rectangle); }
 
+    /// <summary>
+    ///     Removes the specified rectangle from the Clip.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         There is a single clip region for the entire application.
+    ///     </para>
+    /// </remarks>
+    /// <param name="region"></param>
+    public static void ExcludeFromClip (Region? region) { Driver?.Clip?.Exclude (region); }
+
     /// <summary>
     ///     Changes the Clip to the intersection of the current Clip and the <see cref="Frame"/> of this View.
     /// </summary>
@@ -86,7 +96,7 @@ public partial class View
     /// <returns>
     ///     The current Clip, which can be then re-applied <see cref="View.SetClip"/>
     /// </returns>
-    internal Region? ClipFrame ()
+    internal Region? AddFrameToClip ()
     {
         if (Driver is null)
         {
@@ -133,7 +143,7 @@ public partial class View
     /// <returns>
     ///     The current Clip, which can be then re-applied <see cref="View.SetClip"/>
     /// </returns>
-    public Region? ClipViewport ()
+    public Region? AddViewportToClip ()
     {
         if (Driver is null)
         {

+ 2 - 2
Terminal.Gui/View/View.Drawing.Primitives.cs

@@ -137,7 +137,7 @@ public partial class View
             return;
         }
 
-        Region prevClip = ClipViewport ();
+        Region prevClip = AddViewportToClip ();
         Rectangle toClear = ViewportToScreen (rect);
         Attribute prev = SetAttribute (new (color ?? GetNormalColor ().Background));
         Driver.FillRect (toClear);
@@ -155,7 +155,7 @@ public partial class View
             return;
         }
 
-        Region prevClip = ClipViewport ();
+        Region prevClip = AddViewportToClip ();
         Rectangle toClear = ViewportToScreen (rect);
         Driver.FillRect (toClear, rune);
         SetClip (prevClip);

+ 187 - 83
Terminal.Gui/View/View.Drawing.cs

@@ -14,6 +14,9 @@ public partial class View // Drawing APIs
     {
         IEnumerable<View> viewsArray = views as View [] ?? views.ToArray ();
 
+        // The draw context is used to track the region drawn by each view.
+        DrawContext context = new DrawContext ();
+
         foreach (View view in viewsArray)
         {
             if (force)
@@ -21,7 +24,7 @@ public partial class View // Drawing APIs
                 view.SetNeedsDraw ();
             }
 
-            view.Draw ();
+            view.Draw (context);
         }
 
         Margin.DrawMargins (viewsArray);
@@ -40,94 +43,90 @@ public partial class View // Drawing APIs
     ///         See the View Drawing Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/drawing.html"/>.
     ///     </para>
     /// </remarks>
-    public void Draw ()
+    public void Draw (DrawContext? context = null)
     {
         if (!CanBeVisible (this))
         {
             return;
         }
+        Region? originalClip = GetClip ();
 
-        Region? saved = GetClip ();
-
-        // TODO: This can be further optimized by checking NeedsDraw below and only clearing, drawing text, drawing content, etc. if it is true.
+        // TODO: This can be further optimized by checking NeedsDraw below and only
+        // TODO: clearing, drawing text, drawing content, etc. if it is true.
         if (NeedsDraw || SubViewNeedsDraw)
         {
+            // ------------------------------------
             // Draw the Border and Padding.
-            // We clip to the frame to prevent drawing outside the frame.
-            saved = ClipFrame ();
-
-            DoDrawBorderAndPadding ();
-            SetClip (saved);
+            // Note Margin is special-cased and drawn in a separate pass to support
+            // transparent shadows.
+            DoDrawBorderAndPadding (originalClip);
+            SetClip (originalClip);
 
-            // Draw the content within the Viewport
+            // ------------------------------------
+            // Clear the Viewport
             // By default, we clip to the viewport preventing drawing outside the viewport
             // We also clip to the content, but if a developer wants to draw outside the viewport, they can do
             // so via settings. SetClip honors the ViewportSettings.DisableVisibleContentClipping flag.
             // Get our Viewport in screen coordinates
+            originalClip = AddViewportToClip ();
 
-            saved = ClipViewport ();
+            // If no context ...
+            context ??= new DrawContext ();
 
-            // Clear the viewport
             // TODO: Simplify/optimize SetAttribute system.
             DoSetAttribute ();
             DoClearViewport ();
 
-            // Draw the subviews only if needed
+            // ------------------------------------
+            // Draw the subviews first (order matters: Subviews, Text, Content)
             if (SubViewNeedsDraw)
             {
                 DoSetAttribute ();
-                DoDrawSubviews ();
+                DoDrawSubviews (context);
             }
 
+            // ------------------------------------
             // Draw the text
             DoSetAttribute ();
-            DoDrawText ();
+            DoDrawText (context);
 
+            // ------------------------------------
             // Draw the content
             DoSetAttribute ();
-            DoDrawContent ();
+            DoDrawContent (context);
 
+            // ------------------------------------
+            // Draw the line canvas
             // Restore the clip before rendering the line canvas and adornment subviews
             // because they may draw outside the viewport.
-            SetClip (saved);
-
-            saved = ClipFrame ();
-
-            // Draw the line canvas
+            SetClip (originalClip);
+            originalClip = AddFrameToClip ();
             DoRenderLineCanvas ();
 
+            // ------------------------------------
             // 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.
             DoDrawBorderAndPaddingSubViews ();
 
+            // ------------------------------------
             // Advance the diagnostics draw indicator
             Border?.AdvanceDrawIndicator ();
 
             ClearNeedsDraw ();
         }
 
+        // ------------------------------------
         // This causes the Margin to be drawn in a second pass
         // PERFORMANCE: If there is a Margin, it will be redrawn each iteration of the main loop.
         Margin?.CacheClip ();
 
-        // We're done drawing
-        DoDrawComplete ();
-
-        // QUESTION: Should this go before DoDrawComplete? What is more correct?
-        SetClip (saved);
-
-        // Exclude this view (not including Margin) from the Clip
-        if (this is not Adornment)
-        {
-            Rectangle borderFrame = FrameToScreen ();
-
-            if (Border is { })
-            {
-                borderFrame = Border.FrameToScreen ();
-            }
+        // ------------------------------------
+        // Reset the clip to what it was when we started
+        SetClip (originalClip);
 
-            ExcludeFromClip (borderFrame);
-        }
+        // ------------------------------------
+        // We're done drawing - The Clip is reset to what it was before we started.
+        DoDrawComplete (context);
     }
 
     #region DrawAdornments
@@ -144,10 +143,10 @@ public partial class View // Drawing APIs
                     subview.SetNeedsDraw ();
                 }
 
-                LineCanvas.Exclude (new (subview.FrameToScreen()));
+                LineCanvas.Exclude (new (subview.FrameToScreen ()));
             }
 
-            Region? saved = Border?.ClipFrame ();
+            Region? saved = Border?.AddFrameToClip ();
             Border?.DoDrawSubviews ();
             SetClip (saved);
         }
@@ -159,17 +158,33 @@ public partial class View // Drawing APIs
                 subview.SetNeedsDraw ();
             }
 
-            Region? saved = Padding?.ClipFrame ();
+            Region? saved = Padding?.AddFrameToClip ();
             Padding?.DoDrawSubviews ();
             SetClip (saved);
         }
     }
 
-    private void DoDrawBorderAndPadding ()
+    private void DoDrawBorderAndPadding (Region? originalClip)
     {
+        if (this is Adornment)
+        {
+            AddFrameToClip ();
+        }
+        else
+        {
+            // Set the clip to be just the thicknesses of the adornments
+            // TODO: Put this union logic in a method on View? 
+            Region? clipAdornments = Margin!.Thickness.AsRegion (Margin!.FrameToScreen ());
+            clipAdornments?.Combine (Border!.Thickness.AsRegion (Border!.FrameToScreen ()), RegionOp.Union);
+            clipAdornments?.Combine (Padding!.Thickness.AsRegion (Padding!.FrameToScreen ()), RegionOp.Union);
+            clipAdornments?.Combine (originalClip, RegionOp.Intersect);
+            SetClip (clipAdornments);
+        }
+
         if (Margin?.NeedsLayout == true)
         {
             Margin.NeedsLayout = false;
+            // BUGBUG: This should not use ClearFrame as that clears the insides too
             Margin?.ClearFrame ();
             Margin?.Parent?.SetSubViewNeedsDraw ();
         }
@@ -291,27 +306,31 @@ public partial class View // Drawing APIs
 
     #region ClearViewport
 
-    private void DoClearViewport ()
+    internal void DoClearViewport ()
     {
-        if (OnClearingViewport ())
+        if (ViewportSettings.HasFlag (ViewportSettings.Transparent))
         {
             return;
         }
 
-        var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
-        ClearingViewport?.Invoke (this, dev);
-
-        if (dev.Cancel)
+        if (OnClearingViewport ())
         {
             return;
         }
 
-        if (!NeedsDraw)
+        var dev = new DrawEventArgs (Viewport, Rectangle.Empty, null);
+        ClearingViewport?.Invoke (this, dev);
+
+        if (dev.Cancel)
         {
+            SetNeedsDraw ();
             return;
         }
 
         ClearViewport ();
+
+        OnClearedViewport ();
+        ClearedViewport?.Invoke (this, new (Viewport, Viewport, null));
     }
 
     /// <summary>
@@ -320,7 +339,7 @@ public partial class View // Drawing APIs
     /// <returns><see langword="true"/> to stop further clearing.</returns>
     protected virtual bool OnClearingViewport () { return false; }
 
-    /// <summary>Event invoked when the content area of the View is to be drawn.</summary>
+    /// <summary>Event invoked when the <see cref="Viewport"/> is to be cleared.</summary>
     /// <remarks>
     ///     <para>Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.</para>
     ///     <para>
@@ -330,6 +349,14 @@ public partial class View // Drawing APIs
     /// </remarks>
     public event EventHandler<DrawEventArgs>? ClearingViewport;
 
+    /// <summary>
+    ///     Called when the <see cref="Viewport"/> has been cleared
+    /// </summary>
+    protected virtual void OnClearedViewport () { }
+
+    /// <summary>Event invoked when the <see cref="Viewport"/> has been cleared.</summary>
+    public event EventHandler<DrawEventArgs>? ClearedViewport;
+
     /// <summary>Clears <see cref="Viewport"/> with the normal background.</summary>
     /// <remarks>
     ///     <para>
@@ -368,35 +395,41 @@ public partial class View // Drawing APIs
 
     #region DrawText
 
-    private void DoDrawText ()
+    private void DoDrawText (DrawContext? context = null)
     {
-        if (OnDrawingText ())
+        if (OnDrawingText (context))
         {
             return;
         }
 
-        var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
-        DrawingText?.Invoke (this, dev);
-
-        if (dev.Cancel)
+        if (OnDrawingText ())
         {
             return;
         }
 
-        if (!NeedsDraw)
+        var dev = new DrawEventArgs (Viewport, Rectangle.Empty, context);
+        DrawingText?.Invoke (this, dev);
+
+        if (dev.Cancel)
         {
             return;
         }
 
-        DrawText ();
+        DrawText (context);
     }
 
     /// <summary>
     ///     Called when the <see cref="Text"/> of the View is to be drawn.
     /// </summary>
+    /// <param name="context">The draw context to report drawn areas to.</param>
     /// <returns><see langword="true"/> to stop further drawing of  <see cref="Text"/>.</returns>
-    protected virtual bool OnDrawingText () { return false; }
+    protected virtual bool OnDrawingText (DrawContext? context) { return false; }
 
+    /// <summary>
+    ///     Called when the <see cref="Text"/> of the View is to be drawn.
+    /// </summary>
+    /// <returns><see langword="true"/> to stop further drawing of  <see cref="Text"/>.</returns>
+    protected virtual bool OnDrawingText () { return false; }
 
     /// <summary>Raised when the <see cref="Text"/> of the View is to be drawn.</summary>
     /// <returns>
@@ -408,16 +441,27 @@ public partial class View // Drawing APIs
     /// <summary>
     ///     Draws the <see cref="Text"/> of the View using the <see cref="TextFormatter"/>.
     /// </summary>
-    public void DrawText ()
+    /// <param name="context">The draw context to report drawn areas to.</param>
+    public void DrawText (DrawContext? context = null)
     {
         if (!string.IsNullOrEmpty (TextFormatter.Text))
         {
             TextFormatter.NeedsFormat = true;
         }
 
-        // TODO: If the output is not in the Viewport, do nothing
         var drawRect = new Rectangle (ContentToScreen (Point.Empty), GetContentSize ());
 
+        // Use GetDrawRegion to get precise drawn areas
+        Region textRegion = TextFormatter.GetDrawRegion (drawRect);
+
+        // Report the drawn area to the context
+        context?.AddDrawnRegion (textRegion);
+
+        if (!NeedsDraw)
+        {
+            return;
+        }
+
         TextFormatter?.Draw (
                              drawRect,
                              HasFocus ? GetFocusColor () : GetNormalColor (),
@@ -430,34 +474,45 @@ public partial class View // Drawing APIs
     }
 
     #endregion DrawText
-
     #region DrawContent
 
-    private void DoDrawContent ()
+    private void DoDrawContent (DrawContext? context = null)
     {
+        if (OnDrawingContent (context))
+        {
+            return;
+        }
+
         if (OnDrawingContent ())
         {
             return;
         }
 
-        var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
+        var dev = new DrawEventArgs (Viewport, Rectangle.Empty, context);
         DrawingContent?.Invoke (this, dev);
 
         if (dev.Cancel)
-        { }
+        {
+            return;
+        }
 
-        // Do nothing.
+        // No default drawing; let event handlers or overrides handle it
     }
 
     /// <summary>
     ///     Called when the View's content is to be drawn. The default implementation does nothing.
     /// </summary>
-    /// <remarks>
-    /// </remarks>
+    /// <param name="context">The draw context to report drawn areas to.</param>
+    /// <returns><see langword="true"/> to stop further drawing content.</returns>
+    protected virtual bool OnDrawingContent (DrawContext? context = null) { return false; }
+
+    /// <summary>
+    ///     Called when the View's content is to be drawn. The default implementation does nothing.
+    /// </summary>
     /// <returns><see langword="true"/> to stop further drawing content.</returns>
     protected virtual bool OnDrawingContent () { return false; }
 
-    /// <summary>Raised when  the View's content is to be drawn.</summary>
+    /// <summary>Raised when the View's content is to be drawn.</summary>
     /// <remarks>
     ///     <para>Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.</para>
     ///     <para>
@@ -471,14 +526,19 @@ public partial class View // Drawing APIs
 
     #region DrawSubviews
 
-    private void DoDrawSubviews ()
+    private void DoDrawSubviews (DrawContext? context = null)
     {
+        if (OnDrawingSubviews (context))
+        {
+            return;
+        }
+
         if (OnDrawingSubviews ())
         {
             return;
         }
 
-        var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
+        var dev = new DrawEventArgs (Viewport, Rectangle.Empty, context);
         DrawingSubviews?.Invoke (this, dev);
 
         if (dev.Cancel)
@@ -491,15 +551,21 @@ public partial class View // Drawing APIs
             return;
         }
 
-        DrawSubviews ();
+        DrawSubviews (context);
     }
 
     /// <summary>
     ///     Called when the <see cref="Subviews"/> are to be drawn.
     /// </summary>
+    /// <param name="context">The draw context to report drawn areas to, or null if not tracking.</param>
     /// <returns><see langword="true"/> to stop further drawing of <see cref="Subviews"/>.</returns>
-    protected virtual bool OnDrawingSubviews () { return false; }
+    protected virtual bool OnDrawingSubviews (DrawContext? context) { return false; }
 
+    /// <summary>
+    ///     Called when the <see cref="Subviews"/> are to be drawn.
+    /// </summary>
+    /// <returns><see langword="true"/> to stop further drawing of <see cref="Subviews"/>.</returns>
+    protected virtual bool OnDrawingSubviews () { return false; }
 
     /// <summary>Raised when the <see cref="Subviews"/> are to be drawn.</summary>
     /// <remarks>
@@ -513,7 +579,8 @@ public partial class View // Drawing APIs
     /// <summary>
     ///     Draws the <see cref="Subviews"/>.
     /// </summary>
-    public void DrawSubviews ()
+    /// <param name="context">The draw context to report drawn areas to, or null if not tracking.</param>
+    public void DrawSubviews (DrawContext? context = null)
     {
         if (_subviews is null)
         {
@@ -523,12 +590,12 @@ public partial class View // Drawing APIs
         // Draw the subviews in reverse order to leverage clipping.
         foreach (View view in _subviews.Where (view => view.Visible).Reverse ())
         {
-            // TODO: HACK - This enables auto line join to work, but is brute force.
-            if (view.SuperViewRendersLineCanvas)
+            // TODO: HACK - This forcing of SetNeedsDraw with SuperViewRendersLineCanvas enables auto line join to work, but is brute force.
+            if (view.SuperViewRendersLineCanvas || view.ViewportSettings.HasFlag (ViewportSettings.Transparent))
             {
                 view.SetNeedsDraw ();
             }
-            view.Draw ();
+            view.Draw (context);
 
             if (view.SuperViewRendersLineCanvas)
             {
@@ -609,19 +676,56 @@ public partial class View // Drawing APIs
 
     #region DrawComplete
 
-    private void DoDrawComplete ()
+    private void DoDrawComplete (DrawContext? context)
     {
-        OnDrawComplete ();
+        OnDrawComplete (context);
+        DrawComplete?.Invoke (this, new (Viewport, Viewport, context));
+
+        // Now, update the clip to exclude this view (not including Margin)
+        if (this is not Adornment)
+        {
+            if (ViewportSettings.HasFlag (ViewportSettings.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.
+                context!.ClipDrawnRegion (ViewportToScreen (Viewport));
 
-        DrawComplete?.Invoke (this, new (Viewport, Viewport));
+                // Exclude the drawn region from the clip
+                ExcludeFromClip (context!.GetDrawnRegion ());
 
-        // Default implementation does nothing.
+                // Exclude the Border and Padding from the clip
+                ExcludeFromClip (Border?.Thickness.AsRegion (FrameToScreen ()));
+                ExcludeFromClip (Padding?.Thickness.AsRegion (FrameToScreen ()));
+            }
+            else
+            {
+                // Exclude this view (not including Margin) from the Clip
+                Rectangle borderFrame = FrameToScreen ();
+
+                if (Border is { })
+                {
+                    borderFrame = Border.FrameToScreen ();
+                }
+
+                // In the non-transparent (typical case), we want to exclude the entire view area (borderFrame) from the clip
+                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.
+                context?.AddDrawnRectangle (borderFrame);
+            }
+        }
+
+        // TODO: Determine if we need another event that conveys the FINAL DrawContext
     }
 
     /// <summary>
     ///     Called when the View is completed drawing.
     /// </summary>
-    protected virtual void OnDrawComplete () { }
+    /// <remarks>
+    ///     The <paramref name="context"/> parameter provides the drawn region of the View.
+    /// </remarks>
+    protected virtual void OnDrawComplete (DrawContext? context) { }
 
     /// <summary>Raised when the View is completed drawing.</summary>
     /// <remarks>

+ 12 - 2
Terminal.Gui/View/View.Mouse.cs

@@ -763,7 +763,7 @@ public partial class View // Mouse APIs
     /// </summary>
     /// <param name="location"></param>
     /// <returns></returns>
-    internal static List<View?> GetViewsUnderMouse (in Point location)
+    internal static List<View?> GetViewsUnderMouse (in Point location, bool ignoreTransparent = false)
     {
         List<View?> viewsUnderMouse = new ();
 
@@ -810,7 +810,8 @@ public partial class View // Mouse APIs
             for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
             {
                 if (start.InternalSubviews [i].Visible
-                    && start.InternalSubviews [i].Contains (new (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y)))
+                    && start.InternalSubviews [i].Contains (new (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y))
+                    && (!ignoreTransparent || !start.InternalSubviews [i].ViewportSettings.HasFlag (ViewportSettings.TransparentMouse)))
                 {
                     subview = start.InternalSubviews [i];
                     currentLocation.X = startOffsetX + start.Viewport.X;
@@ -823,6 +824,15 @@ public partial class View // Mouse APIs
 
             if (subview is null)
             {
+                if (start.ViewportSettings.HasFlag (ViewportSettings.TransparentMouse))
+                {
+                    viewsUnderMouse.AddRange (View.GetViewsUnderMouse (location, true));
+
+                    // De-dupe viewsUnderMouse
+                    HashSet<View?> dedupe = [..viewsUnderMouse];
+                    viewsUnderMouse = [..dedupe];
+                }
+
                 // No subview was found that's under the mouse, so we're done
                 return viewsUnderMouse;
             }

+ 32 - 10
Terminal.Gui/View/ViewportSettings.cs

@@ -1,7 +1,7 @@
 namespace Terminal.Gui;
 
 /// <summary>
-///     Settings for how the <see cref="View.Viewport"/> behaves relative to the View's Content area.
+///     Settings for how the <see cref="View.Viewport"/> behaves.
 /// </summary>
 /// <remarks>
 ///     See the Layout Deep Dive for more information:
@@ -13,7 +13,7 @@ public enum ViewportSettings
     /// <summary>
     ///     No settings.
     /// </summary>
-    None = 0,
+    None = 0b_0000,
 
     /// <summary>
     ///     If set, <see cref="View.Viewport"/><c>.X</c> can be set to negative values enabling scrolling beyond the left of
@@ -23,7 +23,7 @@ public enum ViewportSettings
     ///         When not set, <see cref="View.Viewport"/><c>.X</c> is constrained to positive values.
     ///     </para>
     /// </summary>
-    AllowNegativeX = 1,
+    AllowNegativeX = 0b_0001,
 
     /// <summary>
     ///     If set, <see cref="View.Viewport"/><c>.Y</c> can be set to negative values enabling scrolling beyond the top of the
@@ -32,7 +32,7 @@ public enum ViewportSettings
     ///         When not set, <see cref="View.Viewport"/><c>.Y</c> is constrained to positive values.
     ///     </para>
     /// </summary>
-    AllowNegativeY = 2,
+    AllowNegativeY = 0b_0010,
 
     /// <summary>
     ///     If set, <see cref="View.Viewport"/><c>.Size</c> can be set to negative coordinates enabling scrolling beyond the
@@ -58,7 +58,7 @@ public enum ViewportSettings
     ///         The practical effect of this is that the last column of the content will always be visible.
     ///     </para>
     /// </summary>
-    AllowXGreaterThanContentWidth = 4,
+    AllowXGreaterThanContentWidth = 0b_0100,
 
     /// <summary>
     ///     If set, <see cref="View.Viewport"/><c>.Y</c> can be set values greater than <see cref="View.GetContentSize ()"/>
@@ -74,7 +74,7 @@ public enum ViewportSettings
     ///         The practical effect of this is that the last row of the content will always be visible.
     ///     </para>
     /// </summary>
-    AllowYGreaterThanContentHeight = 8,
+    AllowYGreaterThanContentHeight = 0b_1000,
 
     /// <summary>
     ///     If set, <see cref="View.Viewport"/><c>.Location</c> can be set values greater than
@@ -102,7 +102,7 @@ public enum ViewportSettings
     ///         This can be useful in infinite scrolling scenarios.
     ///     </para>
     /// </summary>
-    AllowNegativeXWhenWidthGreaterThanContentWidth = 16,
+    AllowNegativeXWhenWidthGreaterThanContentWidth = 0b_0001_0000,
 
     /// <summary>
     ///     If set and <see cref="View.Viewport"/><c>.Height</c> is greater than <see cref="View.GetContentSize ()"/>
@@ -117,7 +117,7 @@ public enum ViewportSettings
     ///         This can be useful in infinite scrolling scenarios.
     ///     </para>
     /// </summary>
-    AllowNegativeYWhenHeightGreaterThanContentHeight = 32,
+    AllowNegativeYWhenHeightGreaterThanContentHeight = 0b_0010_0000,
 
     /// <summary>
     ///     The combination of <see cref="AllowNegativeXWhenWidthGreaterThanContentWidth"/> and
@@ -129,7 +129,7 @@ public enum ViewportSettings
     ///     By default, clipping is applied to the <see cref="View.Viewport"/>. Setting this flag will cause clipping to be
     ///     applied to the visible content area.
     /// </summary>
-    ClipContentOnly = 64,
+    ClipContentOnly = 0b_0100_0000,
 
     /// <summary>
     ///     If set <see cref="View.ClearViewport"/> will clear only the portion of the content
@@ -138,5 +138,27 @@ public enum ViewportSettings
     ///     <see cref="ClipContentOnly"/> must be set for this setting to work (clipping beyond the visible area must be
     ///     disabled).
     /// </summary>
-    ClearContentOnly = 128
+    ClearContentOnly = 0b_1000_0000,
+
+    /// <summary>
+    ///     If set the View will be transparent: The <see cref="View.Viewport"/> will not be cleared when the View is drawn and the clip region
+    ///     will be set to clip the View's <see cref="View.Text"/> and <see cref="View.Subviews"/>.
+    ///     <para>
+    ///         Only the topmost View in a Subview Hierarchy can be transparent. Any subviews of the topmost transparent view
+    ///         will have indeterminate draw behavior.
+    ///     </para>
+    ///     <para>
+    ///         Combine this with <see cref="TransparentMouse"/> to get a view that is both visually transparent and transparent to the mouse.
+    ///     </para>
+    /// </summary>
+    Transparent = 0b_0001_0000_0000,
+
+    /// <summary>
+    ///     If set the View will be transparent to mouse events: Any mouse event that occurs over the View (and it's Subviews) will be passed to the
+    ///     Views below it.
+    ///     <para>
+    ///         Combine this with <see cref="Transparent"/> to get a view that is both visually transparent and transparent to the mouse.
+    ///     </para>
+    /// </summary>
+    TransparentMouse = 0b_0010_0000_0000,
 }

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

@@ -96,8 +96,8 @@ public class CharMap : View, IDesignable
                            };
 
         // Set up the vertical scrollbar. Turn off AutoShow since it's always visible.
-        VerticalScrollBar.AutoShow = false;
-        VerticalScrollBar.Visible = true; // Force always visible
+        VerticalScrollBar.AutoShow = true;
+        VerticalScrollBar.Visible = false;
         VerticalScrollBar.X = Pos.AnchorEnd ();
         VerticalScrollBar.Y = HEADER_HEIGHT; // Header
     }

+ 1 - 3
Terminal.Gui/Views/TileView.cs

@@ -171,8 +171,6 @@ public class TileView : View
     /// <returns></returns>
     public bool IsRootTileView () { return _parentTileView == null; }
 
-    // BUG: v2 fix this hack
-    // QUESTION: Does this need to be fixed before events are refactored?
     /// <summary>Overridden so no Frames get drawn</summary>
     /// <returns></returns>
     protected override bool OnDrawingBorderAndPadding () { return true; }
@@ -181,7 +179,7 @@ public class TileView : View
     protected override bool OnRenderingLineCanvas () { return false; }
 
     /// <inheritdoc/>
-    protected override void OnDrawComplete ()
+    protected override void OnDrawComplete (DrawContext? context)
     {
         if (ColorScheme is { })
         {

+ 1 - 0
Terminal.sln.DotSettings

@@ -386,6 +386,7 @@
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UL/@EntryIndexedValue">UL</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UR/@EntryIndexedValue">UR</s:String>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XOR/@EntryIndexedValue">XOR</s:String>
 	<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/ApplyAutoDetectedRules/@EntryValue">False</s:Boolean>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>

+ 21 - 6
UICatalog/Scenarios/AllViewsTester.cs

@@ -18,10 +18,9 @@ public class AllViewsTester : Scenario
     private Dictionary<string, Type>? _viewClasses;
     private ListView? _classListView;
     private AdornmentsEditor? _adornmentsEditor;
-
     private ArrangementEditor? _arrangementEditor;
-
     private LayoutEditor? _layoutEditor;
+    private ViewportSettingsEditor? _viewportSettingsEditor;
     private FrameView? _settingsPane;
     private RadioGroup? _orientation;
     private string _demoText = "This, that, and the other thing.";
@@ -133,13 +132,27 @@ public class AllViewsTester : Scenario
             AutoSelectAdornments = false,
             SuperViewRendersLineCanvas = true
         };
-        _layoutEditor.Border!.Thickness = new (1);
+        _layoutEditor.Border!.Thickness = new (1, 1, 1, 0);
+
+        _viewportSettingsEditor = new ()
+        {
+            Title = "ViewportSettings [_5]",
+            X = Pos.Right (_arrangementEditor) - 1,
+            Y = Pos.Bottom (_layoutEditor) - Pos.Func (() => _layoutEditor.Frame.Height == 1 ? 0 : 1),
+            Width = Dim.Width (_layoutEditor),
+            Height = Dim.Auto (),
+            CanFocus = true,
+            AutoSelectViewToEdit = false,
+            AutoSelectAdornments = false,
+            SuperViewRendersLineCanvas = true
+        };
+        _viewportSettingsEditor.Border!.Thickness = new (1, 1, 1, 1);
 
         _settingsPane = new ()
         {
-            Title = "Settings [_5]",
+            Title = "Misc Settings [_6]",
             X = Pos.Right (_adornmentsEditor) - 1,
-            Y = Pos.Bottom (_layoutEditor) - Pos.Func (() => _layoutEditor.Frame.Height == 1 ? 0 : 1),
+            Y = Pos.Bottom (_viewportSettingsEditor) - Pos.Func (() => _viewportSettingsEditor.Frame.Height == 1 ? 0 : 1),
             Width = Dim.Width (_layoutEditor),
             Height = Dim.Auto (),
             CanFocus = true,
@@ -237,7 +250,7 @@ public class AllViewsTester : Scenario
         _hostPane.Padding.Diagnostics = ViewDiagnosticFlags.Ruler;
         _hostPane.Padding.ColorScheme = app.ColorScheme;
 
-        app.Add (_classListView, _adornmentsEditor, _arrangementEditor, _layoutEditor, _settingsPane, _eventLog, _hostPane);
+        app.Add (_classListView, _adornmentsEditor, _arrangementEditor, _layoutEditor, _viewportSettingsEditor, _settingsPane, _eventLog, _hostPane);
 
         app.Initialized += App_Initialized;
 
@@ -306,6 +319,7 @@ public class AllViewsTester : Scenario
 
         _hostPane!.Add (_curView);
         _layoutEditor!.ViewToEdit = _curView;
+        _viewportSettingsEditor!.ViewToEdit = _curView;
         _arrangementEditor!.ViewToEdit = _curView;
         _curView.SetNeedsLayout ();
     }
@@ -318,6 +332,7 @@ public class AllViewsTester : Scenario
             _curView.SubviewsLaidOut -= CurrentView_LayoutComplete;
             _hostPane!.Remove (_curView);
             _layoutEditor!.ViewToEdit = null;
+            _viewportSettingsEditor!.ViewToEdit = null;
             _arrangementEditor!.ViewToEdit = null;
 
             _curView.Dispose ();

+ 77 - 9
UICatalog/Scenarios/Arrangement.cs

@@ -49,11 +49,14 @@ public class Arrangement : Scenario
         FrameView testFrame = new ()
         {
             Title = "_1 Test Frame",
+            Text = "This is the text of the Test Frame.\nLine 2.\nLine 3.",
             X = Pos.Right (arrangementEditor),
             Y = 0,
             Width = Dim.Fill (),
             Height = Dim.Fill ()
         };
+        testFrame.TextAlignment = Alignment.Center;
+        testFrame.VerticalTextAlignment = Alignment.Center;
 
         app.Add (testFrame);
 
@@ -64,16 +67,15 @@ public class Arrangement : Scenario
         tiledView3.Height = Dim.Height (tiledView1);
         View tiledView4 = CreateTiledView (3, Pos.Left (tiledView1), Pos.Bottom (tiledView1) - 1);
         tiledView4.Width = Dim.Func (() => tiledView3.Frame.Width + tiledView2.Frame.Width + tiledView1.Frame.Width - 2);
-        testFrame.Add (tiledView4, tiledView3, tiledView2, tiledView1);
 
-        View overlappedView1 = CreateOverlappedView (2, 0, 13);
-        overlappedView1.Title = "Movable _& Sizable";
+        View movableSizeableWithProgress = CreateOverlappedView (2, 10, 8);
+        movableSizeableWithProgress.Title = "Movable _& Sizable";
         View tiledSubView = CreateTiledView (4, 0, 2);
         tiledSubView.Arrangement = ViewArrangement.Fixed;
-        overlappedView1.Add (tiledSubView);
+        movableSizeableWithProgress.Add (tiledSubView);
         tiledSubView = CreateTiledView (5, Pos.Right (tiledSubView), Pos.Top (tiledSubView));
         tiledSubView.Arrangement = ViewArrangement.Fixed;
-        overlappedView1.Add (tiledSubView);
+        movableSizeableWithProgress.Add (tiledSubView);
 
         ProgressBar progressBar = new ()
         {
@@ -81,7 +83,7 @@ public class Arrangement : Scenario
             Width = Dim.Fill (),
             Id = "progressBar"
         };
-        overlappedView1.Add (progressBar);
+        movableSizeableWithProgress.Add (progressBar);
 
         Timer timer = new (10)
         {
@@ -168,9 +170,6 @@ public class Arrangement : Scenario
         overlappedView2.Add (colorPicker);
         overlappedView2.Width = 50;
 
-        testFrame.Add (overlappedView1);
-        testFrame.Add (overlappedView2);
-
         DatePicker datePicker = new ()
         {
             X = 30,
@@ -183,12 +182,27 @@ public class Arrangement : Scenario
             TabStop = TabBehavior.TabGroup,
             Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped
         };
+
+        TransparentView transparentView = new ()
+        {
+            Id = "transparentView",
+            X = 30,
+            Y = 5,
+            Width = 35,
+            Height = 15
+        };
+
+        testFrame.Add (tiledView4, tiledView3, tiledView2, tiledView1);
+        testFrame.Add (overlappedView2);
         testFrame.Add (datePicker);
+        testFrame.Add (movableSizeableWithProgress);
+        testFrame.Add (transparentView);
 
         adornmentsEditor.AutoSelectSuperView = testFrame;
         arrangementEditor.AutoSelectSuperView = testFrame;
 
         testFrame.SetFocus ();
+
         Application.Run (app);
         timer.Close ();
         app.Dispose ();
@@ -299,3 +313,57 @@ public class Arrangement : Scenario
         return keys;
     }
 }
+
+public class TransparentView : FrameView
+{
+    public TransparentView ()
+    {
+        Title = "Transparent View";
+        base.Text = "View.Text.\nThis should be opaque.\nNote how clipping works?";
+        TextFormatter.Alignment = Alignment.Center;
+        TextFormatter.VerticalAlignment = Alignment.Center;
+        Arrangement = ViewArrangement.Overlapped | ViewArrangement.Resizable | ViewArrangement.Movable;
+        ViewportSettings |= Terminal.Gui.ViewportSettings.Transparent | Terminal.Gui.ViewportSettings.TransparentMouse;
+        BorderStyle = LineStyle.RoundedDotted;
+        base.ColorScheme = Colors.ColorSchemes ["Menu"];
+
+        var transparentSubView = new View ()
+        {
+            Text = "Sizable/Movable View with border. Should be opaque. The shadow should be semi-opaque.",
+            Id = "transparentSubView",
+            X = 4,
+            Y = 8,
+            Width = 20,
+            Height = 8,
+            BorderStyle = LineStyle.Dashed,
+            Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
+            ShadowStyle = ShadowStyle.Transparent,
+            //ViewportSettings = Terminal.Gui.ViewportSettings.Transparent
+        };
+        transparentSubView.Border.Thickness = new (1, 1, 1, 1);
+        transparentSubView.ColorScheme = Colors.ColorSchemes ["Dialog"];
+
+        Button button = new Button ()
+        {
+            Title = "_Opaque Shadows No Worky",
+            X = Pos.Center (),
+            Y = 4,
+            ColorScheme = Colors.ColorSchemes ["Dialog"],
+        };
+
+        button.ClearingViewport += (sender, args) =>
+                                   {
+                                       args.Cancel = true;
+                                   };
+
+
+        base.Add (button);
+        base.Add (transparentSubView);
+    }
+
+    /// <inheritdoc />
+    protected override bool OnClearingViewport () { return false; }
+
+    /// <inheritdoc />
+    protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { return false; }
+}

+ 371 - 0
UICatalog/Scenarios/Editors/ViewportSettingsEditor.cs

@@ -0,0 +1,371 @@
+#nullable enable
+using System;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+/// <summary>
+///     Provides an editor UI for View.ViewportSettings.
+/// </summary>
+public sealed class ViewportSettingsEditor : EditorBase
+{
+    public ViewportSettingsEditor ()
+    {
+        Title = "ViewportSettingsEditor";
+        TabStop = TabBehavior.TabGroup;
+
+        Initialized += ViewportSettingsEditor_Initialized;
+    }
+
+    protected override void OnUpdateSettings ()
+    {
+        foreach (View subview in Subviews)
+        {
+            subview.Enabled = ViewToEdit is not Adornment;
+        }
+
+        if (ViewToEdit is null)
+        { }
+    }
+
+    protected override void OnViewToEditChanged ()
+    {
+        if (ViewToEdit is { } and not Adornment)
+        {
+            //ViewToEdit.VerticalScrollBar.AutoShow = true;
+            //ViewToEdit.HorizontalScrollBar.AutoShow = true;
+
+            _contentSizeWidth!.Value = ViewToEdit.GetContentSize ().Width;
+            _contentSizeHeight!.Value = ViewToEdit.GetContentSize ().Height;
+
+            _cbAllowNegativeX!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.AllowNegativeX)
+                                                  ? CheckState.Checked
+                                                  : CheckState.UnChecked;
+
+            _cbAllowNegativeY!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.AllowNegativeY)
+                                                  ? CheckState.Checked
+                                                  : CheckState.UnChecked;
+
+            _cbAllowXGreaterThanContentWidth!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.AllowXGreaterThanContentWidth)
+                                                                 ? CheckState.Checked
+                                                                 : CheckState.UnChecked;
+
+            _cbAllowYGreaterThanContentHeight!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.AllowYGreaterThanContentHeight)
+                                                                  ? CheckState.Checked
+                                                                  : CheckState.UnChecked;
+
+            _cbClearContentOnly!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.ClearContentOnly)
+                                                    ? CheckState.Checked
+                                                    : CheckState.UnChecked;
+
+            _cbClipContentOnly!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.ClipContentOnly)
+                                                   ? CheckState.Checked
+                                                   : CheckState.UnChecked;
+
+            _cbTransparent!.CheckedState = ViewToEdit.ViewportSettings.HasFlag(Terminal.Gui.ViewportSettings.Transparent)
+                                               ? CheckState.Checked
+                                               : CheckState.UnChecked;
+
+            _cbVerticalScrollBar!.CheckedState = ViewToEdit.VerticalScrollBar.Visible ? CheckState.Checked : CheckState.UnChecked;
+            _cbAutoShowVerticalScrollBar!.CheckedState = ViewToEdit.VerticalScrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked;
+            _cbHorizontalScrollBar!.CheckedState = ViewToEdit.HorizontalScrollBar.Visible ? CheckState.Checked : CheckState.UnChecked;
+            _cbAutoShowHorizontalScrollBar!.CheckedState = ViewToEdit.HorizontalScrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked;
+        }
+    }
+
+    private CheckBox? _cbAllowNegativeX;
+    private CheckBox? _cbAllowNegativeY;
+    private CheckBox? _cbAllowXGreaterThanContentWidth;
+    private CheckBox? _cbAllowYGreaterThanContentHeight;
+    private NumericUpDown<int>? _contentSizeWidth;
+    private NumericUpDown<int>? _contentSizeHeight;
+    private CheckBox? _cbClearContentOnly;
+    private CheckBox? _cbClipContentOnly;
+    private CheckBox? _cbTransparent;
+    private CheckBox? _cbVerticalScrollBar;
+    private CheckBox? _cbAutoShowVerticalScrollBar;
+    private CheckBox? _cbHorizontalScrollBar;
+    private CheckBox? _cbAutoShowHorizontalScrollBar;
+
+    private void ViewportSettingsEditor_Initialized (object? s, EventArgs e)
+    {
+        _cbAllowNegativeX = new()
+        {
+            Title = "Allow X < 0",
+            CanFocus = true
+        };
+
+        Add (_cbAllowNegativeX);
+
+        _cbAllowNegativeY = new()
+        {
+            Title = "Allow Y < 0",
+            CanFocus = true
+        };
+
+        Add (_cbAllowNegativeY);
+
+        _cbAllowXGreaterThanContentWidth = new()
+        {
+            Title = "Allow X > Content Width",
+            Y = Pos.Bottom (_cbAllowNegativeX),
+            CanFocus = true
+        };
+
+        _cbAllowNegativeX.CheckedStateChanging += AllowNegativeXToggle;
+        _cbAllowXGreaterThanContentWidth.CheckedStateChanging += AllowXGreaterThanContentWidthToggle;
+
+        Add (_cbAllowXGreaterThanContentWidth);
+
+        void AllowNegativeXToggle (object? sender, CancelEventArgs<CheckState> e)
+        {
+            if (e.NewValue == CheckState.Checked)
+            {
+                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowNegativeX;
+            }
+            else
+            {
+                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowNegativeX;
+            }
+        }
+
+        void AllowXGreaterThanContentWidthToggle (object? sender, CancelEventArgs<CheckState> e)
+        {
+            if (e.NewValue == CheckState.Checked)
+            {
+                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowXGreaterThanContentWidth;
+            }
+            else
+            {
+                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowXGreaterThanContentWidth;
+            }
+        }
+
+        _cbAllowYGreaterThanContentHeight = new()
+        {
+            Title = "Allow Y > Content Height",
+            X = Pos.Right (_cbAllowXGreaterThanContentWidth) + 1,
+            Y = Pos.Bottom (_cbAllowNegativeX),
+            CanFocus = true
+        };
+
+        _cbAllowNegativeY.CheckedStateChanging += AllowNegativeYToggle;
+
+        _cbAllowYGreaterThanContentHeight.CheckedStateChanging += AllowYGreaterThanContentHeightToggle;
+
+        Add (_cbAllowYGreaterThanContentHeight);
+
+        void AllowNegativeYToggle (object? sender, CancelEventArgs<CheckState> e)
+        {
+            if (e.NewValue == CheckState.Checked)
+            {
+                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowNegativeY;
+            }
+            else
+            {
+                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowNegativeY;
+            }
+        }
+
+        void AllowYGreaterThanContentHeightToggle (object? sender, CancelEventArgs<CheckState> e)
+        {
+            if (e.NewValue == CheckState.Checked)
+            {
+                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowYGreaterThanContentHeight;
+            }
+            else
+            {
+                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowYGreaterThanContentHeight;
+            }
+        }
+
+        _cbAllowNegativeY.X = Pos.Left (_cbAllowYGreaterThanContentHeight);
+
+        var labelContentSize = new Label
+        {
+            Title = "ContentSize:",
+            Y = Pos.Bottom (_cbAllowYGreaterThanContentHeight)
+        };
+
+        _contentSizeWidth = new()
+        {
+            X = Pos.Right (labelContentSize) + 1,
+            Y = Pos.Top (labelContentSize),
+            CanFocus = true
+        };
+        _contentSizeWidth.ValueChanging += ContentSizeWidthValueChanged;
+
+        void ContentSizeWidthValueChanged (object? sender, CancelEventArgs<int> e)
+        {
+            if (e.NewValue < 0)
+            {
+                e.Cancel = true;
+
+                return;
+            }
+
+            // BUGBUG: set_ContentSize is supposed to be `protected`. 
+            ViewToEdit!.SetContentSize (ViewToEdit.GetContentSize () with { Width = e.NewValue });
+        }
+
+        var labelComma = new Label
+        {
+            Title = ",",
+            X = Pos.Right (_contentSizeWidth),
+            Y = Pos.Top (labelContentSize)
+        };
+
+        _contentSizeHeight = new()
+        {
+            X = Pos.Right (labelComma) + 1,
+            Y = Pos.Top (labelContentSize),
+            CanFocus = true
+        };
+        _contentSizeHeight.ValueChanging += ContentSizeHeightValueChanged;
+
+        void ContentSizeHeightValueChanged (object? sender, CancelEventArgs<int> e)
+        {
+            if (e.NewValue < 0)
+            {
+                e.Cancel = true;
+
+                return;
+            }
+
+            // BUGBUG: set_ContentSize is supposed to be `protected`. 
+            ViewToEdit?.SetContentSize (ViewToEdit.GetContentSize () with { Height = e.NewValue });
+        }
+
+        _cbClearContentOnly = new()
+        {
+            Title = "ClearContentOnly",
+            X = 0,
+            Y = Pos.Bottom (labelContentSize),
+            CanFocus = true
+        };
+        _cbClearContentOnly.CheckedStateChanging += ClearContentOnlyToggle;
+
+        void ClearContentOnlyToggle (object? sender, CancelEventArgs<CheckState> e)
+        {
+            if (e.NewValue == CheckState.Checked)
+            {
+                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.ClearContentOnly;
+            }
+            else
+            {
+                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.ClearContentOnly;
+            }
+        }
+
+        _cbClipContentOnly = new()
+        {
+            Title = "ClipContentOnly",
+            X = Pos.Right (_cbClearContentOnly) + 1,
+            Y = Pos.Bottom (labelContentSize),
+            CanFocus = true
+        };
+        _cbClipContentOnly.CheckedStateChanging += ClipContentOnlyToggle;
+
+        void ClipContentOnlyToggle (object? sender, CancelEventArgs<CheckState> e)
+        {
+            if (e.NewValue == CheckState.Checked)
+            {
+                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.ClipContentOnly;
+            }
+            else
+            {
+                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.ClipContentOnly;
+            }
+        }
+
+        _cbTransparent = new ()
+        {
+            Title = "Transparent",
+            X = Pos.Right (_cbClipContentOnly) + 1,
+            Y = Pos.Bottom (labelContentSize),
+            CanFocus = true
+        };
+        _cbTransparent.CheckedStateChanging += TransparentToggle;
+
+        void TransparentToggle (object? sender, CancelEventArgs<CheckState> e)
+        {
+            if (e.NewValue == CheckState.Checked)
+            {
+                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.Transparent;
+            }
+            else
+            {
+                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.Transparent;
+            }
+        }
+
+        _cbVerticalScrollBar = new()
+        {
+            Title = "VerticalScrollBar",
+            X = 0,
+            Y = Pos.Bottom (_cbClearContentOnly),
+            CanFocus = false
+        };
+        _cbVerticalScrollBar.CheckedStateChanging += VerticalScrollBarToggle;
+
+        void VerticalScrollBarToggle (object? sender, CancelEventArgs<CheckState> e)
+        {
+            ViewToEdit!.VerticalScrollBar.Visible = e.NewValue == CheckState.Checked;
+        }
+
+        _cbAutoShowVerticalScrollBar = new()
+        {
+            Title = "AutoShow",
+            X = Pos.Right (_cbVerticalScrollBar) + 1,
+            Y = Pos.Top (_cbVerticalScrollBar),
+            CanFocus = false
+        };
+        _cbAutoShowVerticalScrollBar.CheckedStateChanging += AutoShowVerticalScrollBarToggle;
+
+        void AutoShowVerticalScrollBarToggle (object? sender, CancelEventArgs<CheckState> e)
+        {
+            ViewToEdit!.VerticalScrollBar.AutoShow = e.NewValue == CheckState.Checked;
+        }
+
+        _cbHorizontalScrollBar = new()
+        {
+            Title = "HorizontalScrollBar",
+            X = 0,
+            Y = Pos.Bottom (_cbVerticalScrollBar),
+            CanFocus = false
+        };
+        _cbHorizontalScrollBar.CheckedStateChanging += HorizontalScrollBarToggle;
+
+        void HorizontalScrollBarToggle (object? sender, CancelEventArgs<CheckState> e)
+        {
+            ViewToEdit!.HorizontalScrollBar.Visible = e.NewValue == CheckState.Checked;
+        }
+
+        _cbAutoShowHorizontalScrollBar = new()
+        {
+            Title = "AutoShow ",
+            X = Pos.Right (_cbHorizontalScrollBar) + 1,
+            Y = Pos.Top (_cbHorizontalScrollBar),
+            CanFocus = false
+        };
+        _cbAutoShowHorizontalScrollBar.CheckedStateChanging += AutoShowHorizontalScrollBarToggle;
+
+        void AutoShowHorizontalScrollBarToggle (object? sender, CancelEventArgs<CheckState> e)
+        {
+            ViewToEdit!.HorizontalScrollBar.AutoShow = e.NewValue == CheckState.Checked;
+        }
+
+        Add (
+             labelContentSize,
+             _contentSizeWidth,
+             labelComma,
+             _contentSizeHeight,
+             _cbClearContentOnly,
+             _cbClipContentOnly,
+             _cbTransparent,
+             _cbVerticalScrollBar,
+             _cbHorizontalScrollBar,
+             _cbAutoShowVerticalScrollBar,
+             _cbAutoShowHorizontalScrollBar);
+    }
+}

+ 397 - 0
UICatalog/Scenarios/RegionScenario.cs

@@ -0,0 +1,397 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Terminal.Gui;
+using UICatalog;
+using UICatalog.Scenarios;
+
+/// <summary>
+///     Demonstrates creating and drawing regions through mouse dragging.
+/// </summary>
+[ScenarioMetadata ("Regions", "Region Tester")]
+[ScenarioCategory ("Mouse and Keyboard")]
+[ScenarioCategory ("Drawing")]
+public class RegionScenario : Scenario
+{
+    private readonly Region _region = new ();
+    private Point? _dragStart;
+    private bool _isDragging;
+
+
+    public Rune? _previewFillRune = Glyphs.Stipple;
+
+    public Rune? _fillRune = Glyphs.Dot;
+
+    private RegionDrawStyles _drawStyle;
+    private RegionOp _regionOp;
+
+    public override void Main ()
+    {
+        Application.Init ();
+
+        Window app = new ()
+        {
+            Title = GetQuitKeyAndName (),
+            TabStop = TabBehavior.TabGroup,
+
+        };
+        app.Padding.Thickness = new (1);
+
+        var tools = new ToolsView { Title = "Tools", X = Pos.AnchorEnd (), Y = 2 };
+
+        tools.CurrentAttribute = app.ColorScheme.HotNormal;
+
+        tools.SetStyle += b =>
+                          {
+                              _drawStyle = (RegionDrawStyles)b;
+                              app.SetNeedsDraw();
+                          };
+
+        tools.RegionOpChanged += (s, e) =>
+                                {
+                                    _regionOp = e;
+                                };
+        //tools.AddLayer += () => canvas.AddLayer ();
+
+        app.Add (tools);
+
+        // Add drag handling to window
+        app.MouseEvent += (s, e) =>
+                          {
+                              if (e.Flags.HasFlag (MouseFlags.Button1Pressed))
+                              {
+                                  if (!e.Flags.HasFlag (MouseFlags.ReportMousePosition))
+                                  { // Start drag
+                                      _dragStart = e.ScreenPosition;
+                                      _isDragging = true;
+                                  }
+                                  else
+                                  {
+                                      // Drag
+                                      if (_isDragging && _dragStart.HasValue)
+                                      {
+                                          app.SetNeedsDraw ();
+                                      }
+                                  }
+                              }
+
+                              if (e.Flags.HasFlag (MouseFlags.Button1Released))
+                              {
+                                  if (_isDragging && _dragStart.HasValue)
+                                  {
+                                      // Add the new region
+                                      AddRectangleFromPoints (_dragStart.Value, e.ScreenPosition, _regionOp);
+                                      _isDragging = false;
+                                      _dragStart = null;
+                                  }
+
+                                  app.SetNeedsDraw (); 
+                              }
+                          };
+
+        // Draw the regions
+        app.DrawingContent += (s, e) =>
+                              {
+                                  // Draw all regions with single line style
+                                  //_region.FillRectangles (_attribute.Value, _fillRune);
+                                  switch (_drawStyle)
+                                  {
+                                      case RegionDrawStyles.FillOnly:
+                                          _region.FillRectangles (tools.CurrentAttribute!.Value, _previewFillRune);
+
+                                          break;
+
+                                      case RegionDrawStyles.InnerBoundaries:
+                                          _region.DrawBoundaries (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute);
+                                          _region.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' ');
+
+                                          break;
+
+                                      case RegionDrawStyles.OuterBoundary:
+                                          _region.DrawOuterBoundary (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute);
+                                          _region.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' ');
+
+                                          break;
+                                  }
+
+                                  // If currently dragging, draw preview rectangle
+                                  if (_isDragging && _dragStart.HasValue)
+                                  {
+                                      Point currentMousePos = Application.GetLastMousePosition ()!.Value;
+                                      var previewRect = GetRectFromPoints (_dragStart.Value, currentMousePos);
+                                      var previewRegion = new Region (previewRect);
+
+                                      previewRegion.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' ');
+
+                                      previewRegion.DrawBoundaries (app.LineCanvas, LineStyle.Dashed, new (tools.CurrentAttribute!.Value.Foreground.GetHighlightColor(), tools.CurrentAttribute!.Value.Background));
+                                  }
+                              };
+
+        Application.Run (app);
+
+        // Clean up
+        app.Dispose ();
+        Application.Shutdown ();
+    }
+
+    private void AddRectangleFromPoints (Point start, Point end, RegionOp op)
+    {
+        var rect = GetRectFromPoints (start, end);
+        var region = new Region (rect);
+        _region.Combine (region, op); // Or RegionOp.MinimalUnion if you want minimal rectangles
+    }
+
+    private Rectangle GetRectFromPoints (Point start, Point end)
+    {
+        int left = Math.Min (start.X, end.X);
+        int top = Math.Min (start.Y, end.Y);
+        int right = Math.Max (start.X, end.X);
+        int bottom = Math.Max (start.Y, end.Y);
+
+        // Ensure minimum width and height of 1
+        int width = Math.Max (1, right - left + 1);
+        int height = Math.Max (1, bottom - top + 1);
+
+        return new Rectangle (left, top, width, height);
+    }
+}
+
+public enum RegionDrawStyles
+{
+    FillOnly = 0,
+
+    InnerBoundaries = 1,
+
+    OuterBoundary = 2
+
+}
+
+public class ToolsView : Window
+{
+    //private Button _addLayerBtn;
+    private readonly AttributeView _attributeView = new ();
+    private RadioGroup _stylePicker;
+    private RegionOpSelector _regionOpSelector;
+
+    public Attribute? CurrentAttribute
+    {
+        get => _attributeView.Value;
+        set => _attributeView.Value = value;
+    }
+
+    public ToolsView ()
+    {
+        BorderStyle = LineStyle.Dotted;
+        Border!.Thickness = new (1, 2, 1, 1);
+        Height = Dim.Auto ();
+        Width = Dim.Auto ();
+
+    }
+
+    //public event Action AddLayer;
+
+    public override void BeginInit ()
+    {
+        base.BeginInit ();
+
+        _attributeView.ValueChanged += (s, e) => AttributeChanged?.Invoke (this, e);
+
+        _stylePicker = new ()
+        {
+            Width=Dim.Fill(),
+            X = 0, Y = Pos.Bottom (_attributeView) + 1, RadioLabels = Enum.GetNames<RegionDrawStyles> ().Select (n => n = "_" + n).ToArray()
+        };
+        _stylePicker.BorderStyle = LineStyle.Single;
+        _stylePicker.Border.Thickness = new (0, 1, 0, 0);
+        _stylePicker.Title = "Draw Style";
+
+        _stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem); };
+        _stylePicker.SelectedItem = (int)RegionDrawStyles.FillOnly;
+
+        _regionOpSelector = new ()
+        {
+            X = 0,
+            Y = Pos.Bottom (_stylePicker) + 1
+        };
+        _regionOpSelector.SelectedItemChanged += (s, a) => { RegionOpChanged?.Invoke (this, a); };
+        _regionOpSelector.SelectedItem = RegionOp.MinimalUnion;
+
+        //_addLayerBtn = new () { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
+
+        //_addLayerBtn.Accepting += (s, a) => AddLayer?.Invoke ();
+        Add (_attributeView, _stylePicker, _regionOpSelector);//, _addLayerBtn);
+    }
+
+    public event EventHandler<Attribute?>? AttributeChanged;
+    public event EventHandler<RegionOp>? RegionOpChanged;
+    public event Action<LineStyle>? SetStyle;
+}
+
+public class RegionOpSelector : View
+{
+    private RadioGroup _radioGroup;
+    public RegionOpSelector ()
+    {
+        Width = Dim.Auto ();
+        Height = Dim.Auto ();
+
+        BorderStyle = LineStyle.Single;
+        Border.Thickness = new (0, 1, 0, 0);
+        Title = "RegionOp";
+
+        _radioGroup = new ()
+        {
+            X = 0,
+            Y = 0,
+            RadioLabels = Enum.GetNames<RegionOp> ().Select (n => n = "_" + n).ToArray ()
+        };
+        _radioGroup.SelectedItemChanged += (s, a) => { SelectedItemChanged?.Invoke (this, (RegionOp)a.SelectedItem); };
+        Add (_radioGroup);
+    }
+    public event EventHandler<RegionOp>? SelectedItemChanged;
+
+    public RegionOp SelectedItem
+    {
+        get => (RegionOp)_radioGroup.SelectedItem;
+        set => _radioGroup.SelectedItem = (int) value;
+    }
+
+}
+
+public class AttributeView : View
+{
+    public event EventHandler<Attribute?>? ValueChanged;
+    private Attribute? _value;
+
+    public Attribute? Value
+    {
+        get => _value;
+        set
+        {
+            _value = value;
+            ValueChanged?.Invoke (this, value);
+        }
+    }
+
+    private static readonly HashSet<(int, int)> _foregroundPoints =
+    [
+        (0, 0), (1, 0), (2, 0),
+        (0, 1), (1, 1), (2, 1)
+    ];
+
+    private static readonly HashSet<(int, int)> _backgroundPoints =
+    [
+        (3, 1),
+        (1, 2), (2, 2), (3, 2)
+    ];
+
+    public AttributeView ()
+    {
+        Width = Dim.Fill();
+        Height = 4;
+
+        BorderStyle = LineStyle.Single;
+        Border.Thickness = new (0, 1, 0, 0);
+        Title = "Attribute";
+    }
+
+    /// <inheritdoc/>
+    protected override bool OnDrawingContent ()
+    {
+        Color fg = Value?.Foreground ?? Color.Black;
+        Color bg = Value?.Background ?? Color.Black;
+
+        bool isTransparentFg = fg == GetNormalColor ().Background;
+        bool isTransparentBg = bg == GetNormalColor ().Background;
+
+        SetAttribute (new (fg, isTransparentFg ? Color.Gray : fg));
+
+        // Square of foreground color
+        foreach ((int, int) point in _foregroundPoints)
+        {
+            // Make pattern like this when it is same color as background of control
+            /*▓▒
+              ▒▓*/
+            Rune rune;
+
+            if (isTransparentFg)
+            {
+                rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒');
+            }
+            else
+            {
+                rune = (Rune)'█';
+            }
+
+            AddRune (point.Item1, point.Item2, rune);
+        }
+
+        SetAttribute (new (bg, isTransparentBg ? Color.Gray : bg));
+
+        // Square of background color
+        foreach ((int, int) point in _backgroundPoints)
+        {
+            // Make pattern like this when it is same color as background of control
+            /*▓▒
+              ▒▓*/
+            Rune rune;
+
+            if (isTransparentBg)
+            {
+                rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒');
+            }
+            else
+            {
+                rune = (Rune)'█';
+            }
+
+            AddRune (point.Item1, point.Item2, rune);
+        }
+
+        return true;
+    }
+
+    /// <inheritdoc/>
+    protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
+    {
+        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))
+        {
+            if (IsForegroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y))
+            {
+                ClickedInForeground ();
+            }
+            else if (IsBackgroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y))
+            {
+                ClickedInBackground ();
+            }
+
+        }
+        mouseEvent.Handled = true;
+
+        return mouseEvent.Handled;
+    }
+
+    private bool IsForegroundPoint (int x, int y) { return _foregroundPoints.Contains ((x, y)); }
+
+    private bool IsBackgroundPoint (int x, int y) { return _backgroundPoints.Contains ((x, y)); }
+
+    private void ClickedInBackground ()
+    {
+        if (LineDrawing.PromptForColor ("Background", Value!.Value.Background, out Color newColor))
+        {
+            Value = new (Value!.Value.Foreground, newColor);
+            SetNeedsDraw ();
+        }
+    }
+
+    private void ClickedInForeground ()
+    {
+        if (LineDrawing.PromptForColor ("Foreground", Value!.Value.Foreground, out Color newColor))
+        {
+            Value = new (newColor, Value!.Value.Background);
+            SetNeedsDraw ();
+        }
+    }
+}

+ 80 - 62
UICatalog/Scenarios/TextAlignmentAndDirection.cs

@@ -10,6 +10,16 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Text and Formatting")]
 public class TextAlignmentAndDirection : Scenario
 {
+
+    internal class AlignmentAndDirectionView : View
+    {
+        public AlignmentAndDirectionView()
+        {
+            ViewportSettings = Terminal.Gui.ViewportSettings.Transparent;
+            BorderStyle = LineStyle.Dotted;
+        }
+    }
+
     public override void Main ()
     {
         Application.Init ();
@@ -24,8 +34,8 @@ public class TextAlignmentAndDirection : Scenario
         var color1 = new ColorScheme { Normal = new (Color.Black, Color.Gray) };
         var color2 = new ColorScheme { Normal = new (Color.Black, Color.DarkGray) };
 
-        List<Label> singleLineLabels = new (); // single line
-        List<Label> multiLineLabels = new (); // multi line
+        List<View> singleLineLabels = new (); // single line
+        List<View> multiLineLabels = new (); // multi line
 
         // Horizontal Single-Line 
 
@@ -37,7 +47,7 @@ public class TextAlignmentAndDirection : Scenario
             Height = 1,
             TextAlignment = Alignment.End,
             ColorScheme = Colors.ColorSchemes ["Dialog"],
-            Text = "Start"
+            Text = "Start",
         };
 
         var labelHC = new Label
@@ -73,7 +83,7 @@ public class TextAlignmentAndDirection : Scenario
             Text = "Fill"
         };
 
-        var txtLabelHL = new Label
+        var txtLabelHL = new View
         {
             X = Pos.Right (labelHL) + 1,
             Y = Pos.Y (labelHL),
@@ -81,10 +91,11 @@ public class TextAlignmentAndDirection : Scenario
             Height = 1,
             ColorScheme = color1,
             TextAlignment = Alignment.Start,
-            Text = txt
+            Text = txt,
+            ViewportSettings = Terminal.Gui.ViewportSettings.Transparent
         };
 
-        var txtLabelHC = new Label
+        var txtLabelHC = new View
         {
             X = Pos.Right (labelHC) + 1,
             Y = Pos.Y (labelHC),
@@ -92,10 +103,11 @@ public class TextAlignmentAndDirection : Scenario
             Height = 1,
             ColorScheme = color2,
             TextAlignment = Alignment.Center,
-            Text = txt
+            Text = txt,
+            ViewportSettings = Terminal.Gui.ViewportSettings.Transparent
         };
 
-        var txtLabelHR = new Label
+        var txtLabelHR = new View
         {
             X = Pos.Right (labelHR) + 1,
             Y = Pos.Y (labelHR),
@@ -103,10 +115,11 @@ public class TextAlignmentAndDirection : Scenario
             Height = 1,
             ColorScheme = color1,
             TextAlignment = Alignment.End,
-            Text = txt
+            Text = txt,
+            ViewportSettings = Terminal.Gui.ViewportSettings.Transparent
         };
 
-        var txtLabelHJ = new Label
+        var txtLabelHJ = new View
         {
             X = Pos.Right (labelHJ) + 1,
             Y = Pos.Y (labelHJ),
@@ -114,7 +127,8 @@ public class TextAlignmentAndDirection : Scenario
             Height = 1,
             ColorScheme = color2,
             TextAlignment = Alignment.Fill,
-            Text = txt
+            Text = txt,
+            ViewportSettings = Terminal.Gui.ViewportSettings.Transparent
         };
 
         singleLineLabels.Add (txtLabelHL);
@@ -185,7 +199,7 @@ public class TextAlignmentAndDirection : Scenario
         };
         labelVJ.TextFormatter.WordWrap = false;
 
-        var txtLabelVT = new Label
+        var txtLabelVT = new View
         {
             X = Pos.X (labelVT),
             Y = Pos.Bottom (labelVT) + 1,
@@ -194,11 +208,12 @@ public class TextAlignmentAndDirection : Scenario
             ColorScheme = color1,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = Alignment.Start,
-            Text = txt
+            Text = txt,
+            ViewportSettings = Terminal.Gui.ViewportSettings.Transparent
         };
         txtLabelVT.TextFormatter.WordWrap = false;
 
-        var txtLabelVM = new Label
+        var txtLabelVM = new View
         {
             X = Pos.X (labelVM),
             Y = Pos.Bottom (labelVM) + 1,
@@ -207,11 +222,12 @@ public class TextAlignmentAndDirection : Scenario
             ColorScheme = color2,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = Alignment.Center,
-            Text = txt
+            Text = txt,
+            ViewportSettings = Terminal.Gui.ViewportSettings.Transparent
         };
         txtLabelVM.TextFormatter.WordWrap = false;
 
-        var txtLabelVB = new Label
+        var txtLabelVB = new View
         {
             X = Pos.X (labelVB),
             Y = Pos.Bottom (labelVB) + 1,
@@ -220,11 +236,12 @@ public class TextAlignmentAndDirection : Scenario
             ColorScheme = color1,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = Alignment.End,
-            Text = txt
+            Text = txt,
+            ViewportSettings = Terminal.Gui.ViewportSettings.Transparent
         };
         txtLabelVB.TextFormatter.WordWrap = false;
 
-        var txtLabelVJ = new Label
+        var txtLabelVJ = new View
         {
             X = Pos.X (labelVJ),
             Y = Pos.Bottom (labelVJ) + 1,
@@ -233,7 +250,8 @@ public class TextAlignmentAndDirection : Scenario
             ColorScheme = color2,
             TextDirection = TextDirection.TopBottom_LeftRight,
             VerticalTextAlignment = Alignment.Fill,
-            Text = txt
+            Text = txt,
+            ViewportSettings = Terminal.Gui.ViewportSettings.Transparent
         };
         txtLabelVJ.TextFormatter.WordWrap = false;
 
@@ -263,120 +281,120 @@ public class TextAlignmentAndDirection : Scenario
             //ColorScheme = color2
         };
 
-        var txtLabelTL = new Label
+        var txtLabelTL = new AlignmentAndDirectionView
         {
-            X = 0 /*                    */,
+            X = 0,
             Y = 1,
             Width = Dim.Percent (100 / 3),
             Height = Dim.Percent (100 / 3),
             TextAlignment = Alignment.Start,
             VerticalTextAlignment = Alignment.Start,
             ColorScheme = color1,
-            Text = txt
+            Text = txt,
         };
         txtLabelTL.TextFormatter.MultiLine = true;
 
-        var txtLabelTC = new Label
+        var txtLabelTC = new AlignmentAndDirectionView
         {
-            X = Pos.Right (txtLabelTL) + 2,
+            X = Pos.Right (txtLabelTL),
             Y = 1,
-            Width = Dim.Percent (33),
-            Height = Dim.Percent (33),
+            Width = Dim.Percent (100 / 3),
+            Height = Dim.Percent (100 / 3),
             TextAlignment = Alignment.Center,
             VerticalTextAlignment = Alignment.Start,
             ColorScheme = color1,
-            Text = txt
+            Text = txt,
         };
         txtLabelTC.TextFormatter.MultiLine = true;
 
-        var txtLabelTR = new Label
+        var txtLabelTR = new AlignmentAndDirectionView
         {
-            X = Pos.Right (txtLabelTC) + 2,
+            X = Pos.Right (txtLabelTC),
             Y = 1,
             Width = Dim.Percent (100, DimPercentMode.Position),
-            Height = Dim.Percent (33),
+            Height = Dim.Percent (100 / 3),
             TextAlignment = Alignment.End,
             VerticalTextAlignment = Alignment.Start,
             ColorScheme = color1,
-            Text = txt
+            Text = txt,
         };
         txtLabelTR.TextFormatter.MultiLine = true;
 
-        var txtLabelML = new Label
+        var txtLabelML = new AlignmentAndDirectionView
         {
             X = Pos.X (txtLabelTL),
-            Y = Pos.Bottom (txtLabelTL) + 1,
+            Y = Pos.Bottom (txtLabelTL),
             Width = Dim.Width (txtLabelTL),
-            Height = Dim.Percent (33),
+            Height = Dim.Percent (100 / 3),
             TextAlignment = Alignment.Start,
             VerticalTextAlignment = Alignment.Center,
             ColorScheme = color1,
-            Text = txt
+            Text = txt,
         };
         txtLabelML.TextFormatter.MultiLine = true;
 
-        var txtLabelMC = new Label
+        var txtLabelMC = new AlignmentAndDirectionView
         {
             X = Pos.X (txtLabelTC),
-            Y = Pos.Bottom (txtLabelTC) + 1,
+            Y = Pos.Bottom (txtLabelTC),
             Width = Dim.Width (txtLabelTC),
-            Height = Dim.Percent (33),
+            Height = Dim.Percent (100 / 3),
             TextAlignment = Alignment.Center,
             VerticalTextAlignment = Alignment.Center,
             ColorScheme = color1,
-            Text = txt
+            Text = txt,
         };
         txtLabelMC.TextFormatter.MultiLine = true;
 
-        var txtLabelMR = new Label
+        var txtLabelMR = new AlignmentAndDirectionView
         {
             X = Pos.X (txtLabelTR),
-            Y = Pos.Bottom (txtLabelTR) + 1,
+            Y = Pos.Bottom (txtLabelTR),
             Width = Dim.Percent (100, DimPercentMode.Position),
-            Height = Dim.Percent (33),
+            Height = Dim.Percent (100 / 3),
             TextAlignment = Alignment.End,
             VerticalTextAlignment = Alignment.Center,
             ColorScheme = color1,
-            Text = txt
+            Text = txt,
         };
         txtLabelMR.TextFormatter.MultiLine = true;
 
-        var txtLabelBL = new Label
+        var txtLabelBL = new AlignmentAndDirectionView
         {
             X = Pos.X (txtLabelML),
-            Y = Pos.Bottom (txtLabelML) + 1,
+            Y = Pos.Bottom (txtLabelML),
             Width = Dim.Width (txtLabelML),
             Height = Dim.Percent (100, DimPercentMode.Position),
             TextAlignment = Alignment.Start,
             VerticalTextAlignment = Alignment.End,
             ColorScheme = color1,
-            Text = txt
+            Text = txt,
         };
         txtLabelBL.TextFormatter.MultiLine = true;
 
-        var txtLabelBC = new Label
+        var txtLabelBC = new AlignmentAndDirectionView
         {
             X = Pos.X (txtLabelMC),
-            Y = Pos.Bottom (txtLabelMC) + 1,
+            Y = Pos.Bottom (txtLabelMC),
             Width = Dim.Width (txtLabelMC),
             Height = Dim.Percent (100, DimPercentMode.Position),
             TextAlignment = Alignment.Center,
             VerticalTextAlignment = Alignment.End,
             ColorScheme = color1,
-            Text = txt
+            Text = txt,
         };
         txtLabelBC.TextFormatter.MultiLine = true;
 
-        var txtLabelBR = new Label
+        var txtLabelBR = new AlignmentAndDirectionView
         {
             X = Pos.X (txtLabelMR),
-            Y = Pos.Bottom (txtLabelMR) + 1,
+            Y = Pos.Bottom(txtLabelMR),
             Width = Dim.Percent (100, DimPercentMode.Position),
             Height = Dim.Percent (100, DimPercentMode.Position),
             TextAlignment = Alignment.End,
             VerticalTextAlignment = Alignment.End,
             ColorScheme = color1,
-            Text = txt
+            Text = txt,
         };
         txtLabelBR.TextFormatter.MultiLine = true;
 
@@ -391,7 +409,7 @@ public class TextAlignmentAndDirection : Scenario
         multiLineLabels.Add (txtLabelBR);
 
         // Save Alignment in Data
-        foreach (Label t in multiLineLabels)
+        foreach (View t in multiLineLabels)
         {
             t.Data = new { h = t.TextAlignment, v = t.VerticalTextAlignment };
         }
@@ -432,12 +450,12 @@ public class TextAlignmentAndDirection : Scenario
 
         editText.MouseClick += (s, m) =>
                                {
-                                   foreach (Label v in singleLineLabels)
+                                   foreach (View v in singleLineLabels)
                                    {
                                        v.Text = editText.Text;
                                    }
 
-                                   foreach (Label v in multiLineLabels)
+                                   foreach (View v in multiLineLabels)
                                    {
                                        v.Text = editText.Text;
                                    }
@@ -445,12 +463,12 @@ public class TextAlignmentAndDirection : Scenario
 
         app.KeyUp += (s, m) =>
                      {
-                         foreach (Label v in singleLineLabels)
+                         foreach (View v in singleLineLabels)
                          {
                              v.Text = editText.Text;
                          }
 
-                         foreach (Label v in multiLineLabels)
+                         foreach (View v in multiLineLabels)
                          {
                              v.Text = editText.Text;
                          }
@@ -506,14 +524,14 @@ public class TextAlignmentAndDirection : Scenario
                                 {
                                     if (e.CurrentValue == CheckState.Checked)
                                     {
-                                        foreach (Label t in multiLineLabels)
+                                        foreach (View t in multiLineLabels)
                                         {
                                             t.TextFormatter.WordWrap = false;
                                         }
                                     }
                                     else
                                     {
-                                        foreach (Label t in multiLineLabels)
+                                        foreach (View t in multiLineLabels)
                                         {
                                             t.TextFormatter.WordWrap = true;
                                         }
@@ -543,7 +561,7 @@ public class TextAlignmentAndDirection : Scenario
                                                         ToggleJustify (true);
                                                     }
 
-                                                    foreach (Label v in multiLineLabels)
+                                                    foreach (View v in multiLineLabels)
                                                     {
                                                         v.TextDirection = (TextDirection)ev.SelectedItem;
                                                     }
@@ -569,7 +587,7 @@ public class TextAlignmentAndDirection : Scenario
                     justifyOptions.Enabled = false;
                 }
 
-                foreach (Label t in multiLineLabels)
+                foreach (View t in multiLineLabels)
                 {
                     t.TextAlignment = (Alignment)((dynamic)t.Data).h;
                     t.VerticalTextAlignment = (Alignment)((dynamic)t.Data).v;
@@ -577,7 +595,7 @@ public class TextAlignmentAndDirection : Scenario
             }
             else
             {
-                foreach (Label t in multiLineLabels)
+                foreach (View t in multiLineLabels)
                 {
                     if (!wasJustOptions)
                     {

+ 118 - 0
UICatalog/Scenarios/Transparent.cs

@@ -0,0 +1,118 @@
+#nullable enable
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Transparent", "Testing Transparency")]
+public sealed class Transparent : Scenario
+{
+    public override void Main ()
+    {
+        // Init
+        Application.Init ();
+
+        // Setup - Create a top-level application window and configure it.
+        Window appWindow = new ()
+        {
+            Title = GetQuitKeyAndName (),
+        };
+        appWindow.BorderStyle = LineStyle.None;
+        appWindow.ColorScheme = Colors.ColorSchemes ["Error"];
+
+        appWindow.Text = "App Text - Centered Vertically and Horizontally.\n2nd Line of Text.\n3rd Line of Text.";
+        appWindow.TextAlignment = Alignment.Center;
+        appWindow.VerticalTextAlignment = Alignment.Center;
+        appWindow.ClearingViewport += (s, e) =>
+                                    {
+                                        appWindow!.FillRect (appWindow!.Viewport, Glyphs.Stipple);
+                                        e.Cancel = true;
+                                    };
+        ViewportSettingsEditor viewportSettingsEditor = new ViewportSettingsEditor ()
+        {
+            Y = Pos.AnchorEnd (),
+            //X = Pos.Right (adornmentsEditor),
+            AutoSelectViewToEdit = true
+        };
+        appWindow.Add (viewportSettingsEditor);
+
+        Button appButton = new Button ()
+        {
+            X = 10,
+            Y = 4,
+            Title = "_AppButton",
+        };
+        appButton.Accepting += (sender, args) => MessageBox.Query ("AppButton", "Transparency is cool!", "_Ok");
+        appWindow.Add (appButton);
+
+
+        var tv = new TransparentView ()
+        {
+            X = 3,
+            Y = 3,
+            Width = 50,
+            Height = 15
+        };
+        appWindow.Add (tv);
+
+        // Run - Start the application.
+        Application.Run (appWindow);
+        appWindow.Dispose ();
+
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
+    }
+
+    public class TransparentView : FrameView
+    {
+        public TransparentView ()
+        {
+            Title = "Transparent View";
+            base.Text = "View.Text.\nThis should be opaque.\nNote how clipping works?";
+            TextFormatter.Alignment = Alignment.Center;
+            TextFormatter.VerticalAlignment = Alignment.Center;
+            Arrangement = ViewArrangement.Overlapped | ViewArrangement.Resizable | ViewArrangement.Movable;
+            ViewportSettings |= Terminal.Gui.ViewportSettings.Transparent | Terminal.Gui.ViewportSettings.TransparentMouse;
+            BorderStyle = LineStyle.RoundedDotted;
+            base.ColorScheme = Colors.ColorSchemes ["Base"];
+
+            var transparentSubView = new View ()
+            {
+                Text = "Sizable/Movable View with border. Should be opaque. The shadow should be semi-opaque.",
+                Id = "transparentSubView",
+                X = 4,
+                Y = 8,
+                Width = 20,
+                Height = 8,
+                BorderStyle = LineStyle.Dashed,
+                Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
+                ShadowStyle = ShadowStyle.Transparent,
+            };
+            transparentSubView.Border.Thickness = new (1, 1, 1, 1);
+            transparentSubView.ColorScheme = Colors.ColorSchemes ["Dialog"];
+
+            Button button = new Button ()
+            {
+                Title = "_Opaque Shadows No Worky",
+                X = Pos.Center (),
+                Y = 4,
+                ColorScheme = Colors.ColorSchemes ["Dialog"],
+            };
+
+            button.ClearingViewport += (sender, args) =>
+                                       {
+                                           args.Cancel = true;
+                                       };
+
+
+            base.Add (button);
+            base.Add (transparentSubView);
+        }
+
+        /// <inheritdoc />
+        protected override bool OnClearingViewport () { return false; }
+
+        /// <inheritdoc />
+        protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { return false; }
+    }
+
+}

+ 31 - 287
UICatalog/Scenarios/ViewportSettings.cs

@@ -12,17 +12,17 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Adornments")]
 public class ViewportSettings : Scenario
 {
-    public class ScrollingDemoView : FrameView
+    public class ViewportSettingsDemoView : FrameView
     {
-        public ScrollingDemoView ()
+        public ViewportSettingsDemoView ()
         {
-            Id = "ScrollingDemoView";
+            Id = "ViewportSettingsDemoView";
             Width = Dim.Fill ();
             Height = Dim.Fill ();
             base.ColorScheme = Colors.ColorSchemes ["Base"];
 
             base.Text =
-                "Text (ScrollingDemoView.Text). This is long text.\nThe second line.\n3\n4\n5th line\nLine 6. This is a longer line that should wrap automatically.";
+                "Text (ViewportSettingsDemoView.Text). This is long text.\nThe second line.\n3\n4\n5th line\nLine 6. This is a longer line that should wrap automatically.";
             CanFocus = true;
             BorderStyle = LineStyle.Rounded;
             Arrangement = ViewArrangement.Resizable;
@@ -30,6 +30,7 @@ public class ViewportSettings : Scenario
             SetContentSize (new (60, 40));
             ViewportSettings |= Terminal.Gui.ViewportSettings.ClearContentOnly;
             ViewportSettings |= Terminal.Gui.ViewportSettings.ClipContentOnly;
+            VerticalScrollBar.Visible = true;
 
             // Things this view knows how to do
             AddCommand (Command.ScrollDown, () => ScrollVertical (1));
@@ -102,297 +103,33 @@ public class ViewportSettings : Scenario
             Title = GetQuitKeyAndName (),
 
             // Use a different colorscheme so ViewSettings.ClearContentOnly is obvious
-            ColorScheme = Colors.ColorSchemes ["Toplevel"]
+            ColorScheme = Colors.ColorSchemes ["Toplevel"],
+            BorderStyle = LineStyle.None
         };
 
-        var editor = new AdornmentsEditor
+        var adornmentsEditor = new AdornmentsEditor
         {
+            X = Pos.AnchorEnd(),
             AutoSelectViewToEdit = true,
             ShowViewIdentifier = true
         };
-        app.Add (editor);
+        app.Add (adornmentsEditor);
 
-        var view = new ScrollingDemoView
+        ViewportSettingsEditor viewportSettingsEditor = new ViewportSettingsEditor ()
         {
-            Title = "Demo View",
-            X = Pos.Right (editor),
-            Width = Dim.Fill (),
-            Height = Dim.Fill ()
+            Y = Pos.AnchorEnd(),
+            //X = Pos.Right (adornmentsEditor),
         };
+        app.Add (viewportSettingsEditor);
 
-        app.Add (view);
-
-        // Add Scroll Setting UI to Padding
-        view.Padding!.Thickness = view.Padding.Thickness with { Top = view.Padding.Thickness.Top + 6 };
-        view.Padding.CanFocus = true;
-
-        Label frameLabel = new ()
-        {
-            Text = "Frame\nContent",
-            Id = "frameLabel",
-            Y = 0
-        };
-        view.Padding.Add (frameLabel);
-
-        var cbAllowNegativeX = new CheckBox
-        {
-            Title = "Allow _X < 0",
-            Y = Pos.Bottom (frameLabel),
-            CanFocus = true
-        };
-        cbAllowNegativeX.CheckedState = view.ViewportSettings.HasFlag  (Terminal.Gui.ViewportSettings.AllowNegativeX) ? CheckState.Checked : CheckState.UnChecked;
-
-        view.Padding.Add (cbAllowNegativeX);
-
-        var cbAllowNegativeY = new CheckBox
-        {
-            Title = "Allow _Y < 0",
-            X = Pos.Right (cbAllowNegativeX) + 1,
-            Y = Pos.Bottom (frameLabel),
-            CanFocus = true,
-        };
-        cbAllowNegativeY.CheckedState = view.ViewportSettings.HasFlag  (Terminal.Gui.ViewportSettings.AllowNegativeY) ? CheckState.Checked : CheckState.UnChecked;
-
-        view.Padding.Add (cbAllowNegativeY);
-
-        var cbAllowXGreaterThanContentWidth = new CheckBox
-        {
-            Title = "All_ow X > Content",
-            Y = Pos.Bottom (cbAllowNegativeX),
-            CanFocus = true
-        };
-        cbAllowXGreaterThanContentWidth.CheckedState = view.ViewportSettings.HasFlag  (Terminal.Gui.ViewportSettings.AllowXGreaterThanContentWidth) ? CheckState.Checked : CheckState.UnChecked;
-
-        view.Padding.Add (cbAllowXGreaterThanContentWidth);
-
-        void AllowNegativeXToggle (object sender, CancelEventArgs<CheckState> e)
-        {
-            if (e.NewValue == CheckState.Checked)
-            {
-                view.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowNegativeX;
-            }
-            else
-            {
-                view.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowNegativeX;
-            }
-        }
-
-        void AllowXGreaterThanContentWidthToggle (object sender, CancelEventArgs<CheckState> e)
-        {
-            if (e.NewValue == CheckState.Checked)
-            {
-                view.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowXGreaterThanContentWidth;
-            }
-            else
-            {
-                view.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowXGreaterThanContentWidth;
-            }
-        }
-
-        var cbAllowYGreaterThanContentHeight = new CheckBox
-        {
-            Title = "Allo_w Y > Content",
-            X = Pos.Right (cbAllowXGreaterThanContentWidth) + 1,
-            Y = Pos.Bottom (cbAllowNegativeX),
-            CanFocus = true
-        };
-        cbAllowYGreaterThanContentHeight.CheckedState = view.ViewportSettings.HasFlag  (Terminal.Gui.ViewportSettings.AllowYGreaterThanContentHeight) ? CheckState.Checked : CheckState.UnChecked;
-
-        view.Padding.Add (cbAllowYGreaterThanContentHeight);
-
-        void AllowNegativeYToggle (object sender, CancelEventArgs<CheckState> e)
-        {
-            if (e.NewValue == CheckState.Checked)
-            {
-                view.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowNegativeY;
-            }
-            else
-            {
-                view.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowNegativeY;
-            }
-        }
-
-        void AllowYGreaterThanContentHeightToggle (object sender, CancelEventArgs<CheckState> e)
-        {
-            if (e.NewValue == CheckState.Checked)
-            {
-                view.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowYGreaterThanContentHeight;
-            }
-            else
-            {
-                view.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowYGreaterThanContentHeight;
-            }
-        }
-
-        var labelContentSize = new Label
-        {
-            Title = "ContentSi_ze:",
-            Y = Pos.Bottom (cbAllowYGreaterThanContentHeight)
-        };
-
-        NumericUpDown<int> contentSizeWidth = new NumericUpDown<int>
-        {
-            Value = view.GetContentSize ().Width,
-            X = Pos.Right (labelContentSize) + 1,
-            Y = Pos.Top (labelContentSize),
-            CanFocus = true
-        };
-        contentSizeWidth.ValueChanging += ContentSizeWidthValueChanged;
-
-        void ContentSizeWidthValueChanged (object sender, CancelEventArgs<int> e)
-        {
-            if (e.NewValue < 0)
-            {
-                e.Cancel = true;
-
-                return;
-            }
-            // BUGBUG: set_ContentSize is supposed to be `protected`. 
-            view.SetContentSize (view.GetContentSize () with { Width = e.NewValue });
-        }
-
-        var labelComma = new Label
-        {
-            Title = ",",
-            X = Pos.Right (contentSizeWidth),
-            Y = Pos.Top (labelContentSize)
-        };
-
-        NumericUpDown<int> contentSizeHeight = new NumericUpDown<int>
-        {
-            Value = view.GetContentSize ().Height,
-            X = Pos.Right (labelComma) + 1,
-            Y = Pos.Top (labelContentSize),
-            CanFocus = true
-        };
-        contentSizeHeight.ValueChanging += ContentSizeHeightValueChanged;
-
-        void ContentSizeHeightValueChanged (object sender, CancelEventArgs<int> e)
+        var view = new ViewportSettingsDemoView
         {
-            if (e.NewValue < 0)
-            {
-                e.Cancel = true;
-
-                return;
-            }
-            // BUGBUG: set_ContentSize is supposed to be `protected`. 
-            view.SetContentSize (view.GetContentSize () with { Height = e.NewValue });
-        }
-
-        var cbClearContentOnly = new CheckBox
-        {
-            Title = "C_learContentOnly",
-            X = Pos.Right (contentSizeHeight) + 1,
-            Y = Pos.Top (labelContentSize),
-            CanFocus = true
+            Title = "ViewportSettings Demo View",
+            Width = Dim.Fill (Dim.Func (() => app.IsInitialized ? adornmentsEditor.Frame.Width+1: 1)),
+            Height = Dim.Fill (Dim.Func (() => app.IsInitialized ? viewportSettingsEditor.Frame.Height : 1))
         };
-        cbClearContentOnly.CheckedState = view.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.ClearContentOnly) ? CheckState.Checked : CheckState.UnChecked;
-        cbClearContentOnly.CheckedStateChanging += ClearContentOnlyToggle;
 
-        void ClearContentOnlyToggle (object sender, CancelEventArgs<CheckState> e)
-        {
-            if (e.NewValue == CheckState.Checked)
-            {
-                view.ViewportSettings |= Terminal.Gui.ViewportSettings.ClearContentOnly;
-            }
-            else
-            {
-                view.ViewportSettings &= ~Terminal.Gui.ViewportSettings.ClearContentOnly;
-            }
-        }
-
-        var cbClipContentOnly = new CheckBox
-        {
-            Title = "_ClipContentOnly",
-            X = Pos.Right (cbClearContentOnly) + 1,
-            Y = Pos.Top (labelContentSize),
-            CanFocus = true
-        };
-        cbClipContentOnly.CheckedState = view.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.ClipContentOnly) ? CheckState.Checked : CheckState.UnChecked;
-        cbClipContentOnly.CheckedStateChanging += ClipContentOnlyOnlyToggle;
-
-        void ClipContentOnlyOnlyToggle (object sender, CancelEventArgs<CheckState> e)
-        {
-            if (e.NewValue == CheckState.Checked)
-            {
-                view.ViewportSettings |= Terminal.Gui.ViewportSettings.ClipContentOnly;
-            }
-            else
-            {
-                view.ViewportSettings &= ~Terminal.Gui.ViewportSettings.ClipContentOnly;
-            }
-        }
-
-        var cbVerticalScrollBar = new CheckBox
-        {
-            Title = "_VerticalScrollBar.Visible",
-            X = 0,
-            Y = Pos.Bottom (labelContentSize),
-            CanFocus = false
-        };
-        cbVerticalScrollBar.CheckedState = view.VerticalScrollBar.Visible ? CheckState.Checked : CheckState.UnChecked;
-        cbVerticalScrollBar.CheckedStateChanging += VerticalScrollBarToggle;
-
-        void VerticalScrollBarToggle (object sender, CancelEventArgs<CheckState> e)
-        {
-            view.VerticalScrollBar.Visible = e.NewValue == CheckState.Checked;
-        }
-
-        var cbHorizontalScrollBar = new CheckBox
-        {
-            Title = "_HorizontalScrollBar.Visible",
-            X = Pos.Right (cbVerticalScrollBar) + 1,
-            Y = Pos.Bottom (labelContentSize),
-            CanFocus = false,
-        };
-        cbHorizontalScrollBar.CheckedState = view.HorizontalScrollBar.Visible ? CheckState.Checked : CheckState.UnChecked;
-        cbHorizontalScrollBar.CheckedStateChanging += HorizontalScrollBarToggle;
-
-        void HorizontalScrollBarToggle (object sender, CancelEventArgs<CheckState> e)
-        {
-            view.HorizontalScrollBar.Visible = e.NewValue == CheckState.Checked;
-        }
-
-        view.VerticalScrollBar.AutoShow = true;
-        var cbAutoShowVerticalScrollBar = new CheckBox
-        {
-            Title = "VerticalScrollBar._AutoShow",
-            X = Pos.Right (cbHorizontalScrollBar) + 1,
-            Y = Pos.Bottom (labelContentSize),
-            CanFocus = false,
-            CheckedState = view.VerticalScrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked
-        };
-        cbAutoShowVerticalScrollBar.CheckedStateChanging += AutoShowVerticalScrollBarToggle;
-
-        void AutoShowVerticalScrollBarToggle (object sender, CancelEventArgs<CheckState> e)
-        {
-            view.VerticalScrollBar.AutoShow = e.NewValue == CheckState.Checked;
-        }
-
-        view.HorizontalScrollBar.AutoShow = true;
-        var cbAutoShowHorizontalScrollBar = new CheckBox
-        {
-            Title = "HorizontalScrollBar.A_utoShow ",
-            X = Pos.Right (cbAutoShowVerticalScrollBar) + 1,
-            Y = Pos.Bottom (labelContentSize),
-            CanFocus = false,
-            CheckedState = view.HorizontalScrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked
-        };
-        cbAutoShowHorizontalScrollBar.CheckedStateChanging += AutoShowHorizontalScrollBarToggle;
-
-        void AutoShowHorizontalScrollBarToggle (object sender, CancelEventArgs<CheckState> e)
-        {
-            view.HorizontalScrollBar.AutoShow = e.NewValue == CheckState.Checked;
-        }
-
-        cbAllowNegativeX.CheckedStateChanging += AllowNegativeXToggle;
-        cbAllowNegativeY.CheckedStateChanging += AllowNegativeYToggle;
-
-        cbAllowXGreaterThanContentWidth.CheckedStateChanging += AllowXGreaterThanContentWidthToggle;
-        cbAllowYGreaterThanContentHeight.CheckedStateChanging += AllowYGreaterThanContentHeightToggle;
-
-
-        view.Padding.Add (labelContentSize, contentSizeWidth, labelComma, contentSizeHeight, cbClearContentOnly, cbClipContentOnly, cbVerticalScrollBar, cbHorizontalScrollBar, cbAutoShowVerticalScrollBar, cbAutoShowHorizontalScrollBar);
+        app.Add (view);
 
         // Add demo views to show that things work correctly
         var textField = new TextField { X = 20, Y = 7, Width = 15, Text = "Test Te_xtField" };
@@ -436,7 +173,7 @@ public class ViewportSettings : Scenario
 
         var buttonAnchored = new Button
         {
-            X = Pos.AnchorEnd (), Y = Pos.AnchorEnd (), Text = "Bottom Rig_ht"
+            X = Pos.AnchorEnd () - 10, Y = Pos.AnchorEnd () - 4, Text = "Bottom Rig_ht"
         };
         buttonAnchored.Accepting += (sender, args) => MessageBox.Query ("Hi", $"You pressed {((Button)sender)?.Text}", "_Ok");
 
@@ -475,12 +212,19 @@ public class ViewportSettings : Scenario
         };
         view.Add (slider);
 
-        editor.Initialized += (s, e) => { editor.ViewToEdit = view; };
+        adornmentsEditor.Initialized += (s, e) =>
+                              {
+                                  adornmentsEditor.ViewToEdit = view;
+                              };
 
-        editor.AutoSelectViewToEdit = true;
-        editor.AutoSelectSuperView = view;
-        editor.AutoSelectAdornments = false;
+        adornmentsEditor.AutoSelectViewToEdit = true;
+        adornmentsEditor.AutoSelectSuperView = view;
+        adornmentsEditor.AutoSelectAdornments = false;
 
+        view.Initialized += (s, e) =>
+                                              {
+                                                  viewportSettingsEditor.ViewToEdit = view;
+                                              };
         view.SetFocus ();
         Application.Run (app);
         app.Dispose ();

+ 166 - 0
UnitTests/Drawing/Region/DifferenceTests.cs

@@ -0,0 +1,166 @@
+using Xunit.Sdk;
+
+namespace Terminal.Gui.DrawingTests;
+
+public class DifferenceTests
+{
+
+    [Fact]
+    public void Difference_Rectangle_ExcludesFromRegion ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        region.Combine (new Rectangle (20, 20, 20, 20), RegionOp.Difference);
+        Assert.False (region.Contains (25, 25));
+        Assert.True (region.Contains (15, 15));
+    }
+
+    [Fact]
+    public void Difference_Region_ExcludesRegions ()
+    {
+        var region1 = new Region (new (10, 10, 50, 50));
+        var region2 = new Region (new (20, 20, 20, 20));
+        region1.Combine (region2, RegionOp.Difference);
+        Assert.False (region1.Contains (25, 25));
+        Assert.True (region1.Contains (15, 15));
+    }
+
+    [Fact]
+    public void Difference_WithRectangle_ExcludesRectangle ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        var rect = new Rectangle (30, 30, 50, 50);
+
+        region.Combine (rect, RegionOp.Difference);
+
+        Assert.True (region.Contains (20, 20));
+        Assert.False (region.Contains (35, 35));
+    }
+
+    [Fact]
+    public void Difference_WithRegion_ExcludesRegion ()
+    {
+        var region1 = new Region (new (10, 10, 50, 50));
+        var region2 = new Region (new (30, 30, 50, 50));
+
+        region1.Combine (region2, RegionOp.Difference);
+
+        Assert.True (region1.Contains (20, 20));
+        Assert.False (region1.Contains (35, 35));
+    }
+
+    [Fact]
+    public void Difference_ContainedRectangle_ExcludesRectangle ()
+    {
+
+        /*
+        INPUT: (top-left origin, x→, y↓):
+
+           x=0 1 2 3 4 5
+        y=0  A A A A A A
+        y=1  A A A A A A
+        y=2  A A B B A A
+        y=3  A A B B A A
+        y=4  A A A A A A
+        y=5  A A A A A A
+
+        */
+
+
+        var regionA = new Region (new (0, 0, 6, 6));
+        var rectangleB = new Rectangle (2, 2, 2, 2);
+
+        regionA.Combine (rectangleB, RegionOp.Difference);
+
+        Assert.True (regionA.Contains (0, 0));
+        Assert.True (regionA.Contains (1, 1));
+        Assert.True (regionA.Contains (4, 4));
+        Assert.True (regionA.Contains (5, 5));
+
+        Assert.False (regionA.Contains (2, 2));
+        Assert.False (regionA.Contains (3, 3));
+    }
+
+    [Fact]
+    public void Difference_ContainedRegion_ExcludesRegion ()
+    {
+
+        /*
+        INPUT: (top-left origin, x→, y↓):
+
+           x=0 1 2 3 4 5
+        y=0  A A A A A A
+        y=1  A A A A A A
+        y=2  A A B B A A
+        y=3  A A B B A A
+        y=4  A A A A A A
+        y=5  A A A A A A
+
+        */
+
+
+        var regionA = new Region (new (0, 0, 6, 6));
+        var regionB = new Region (new (2, 2, 2, 2));
+
+        regionA.Combine (regionB, RegionOp.Difference);
+
+        Assert.True (regionA.Contains (0, 0));
+        Assert.True (regionA.Contains (1, 1));
+        Assert.True (regionA.Contains (4, 4));
+        Assert.True (regionA.Contains (5, 5));
+
+        Assert.False (regionA.Contains (2, 2));
+        Assert.False (regionA.Contains (3, 3));
+    }
+
+    [Fact]
+    public void Difference_NonRectangularRegion_ExcludesRegion ()
+    {
+
+        /*
+        INPUT: (top-left origin, x→, y↓):
+
+           x=0 1 2 3 4 5
+        y=0  A A A A A A
+        y=1  A A A A A A
+        y=2  A A B B A A
+        y=3  A A B A A A
+        y=4  A A A A A A
+        y=5  A A A A A A
+
+        */
+
+        var regionA = new Region (new (0, 0, 6, 6));
+
+        var regionB = new Region ();
+        regionB.Combine (new Rectangle (2, 2, 2, 1), RegionOp.MinimalUnion);
+        regionB.Combine (new Rectangle (2, 3, 1, 1), RegionOp.MinimalUnion);
+
+        // regionB is a non-rectangular region that looks like this:
+        // x=   0 1 2 3 4 5
+        // y=0  . . . . . .
+        // y=1  . . . . . .
+        // y=2  . . B B . .
+        // y=3  . . B . . .
+        // y=4  . . . . . .
+        // y=5  . . . . . .
+
+        Assert.True (regionB.Contains (2, 2));
+        Assert.True (regionB.Contains (3, 2));
+        Assert.True (regionB.Contains (2, 3));
+        Assert.False (regionB.Contains (3, 3));
+
+        regionA.Combine (regionB, RegionOp.Difference);
+
+        Assert.True (regionA.Contains (0, 0));
+        Assert.True (regionA.Contains (1, 1));
+        Assert.True (regionA.Contains (3, 3));
+        Assert.True (regionA.Contains (4, 4));
+        Assert.True (regionA.Contains (5, 5));
+
+        Assert.False (regionA.Contains (2, 2));
+        Assert.False (regionA.Contains (3, 2));
+        Assert.False (regionA.Contains (2, 3));
+
+    }
+
+}

+ 188 - 0
UnitTests/Drawing/Region/MergeRectanglesTests.cs

@@ -0,0 +1,188 @@
+namespace Terminal.Gui.DrawingTests;
+
+
+public class MergeRectanglesTests
+{
+
+    [Fact]
+    public void MergeRectangles_ComplexAdjacentRectangles_NoOverlap ()
+    {
+        /*
+            INPUT: Complex arrangement of four adjacent rectangles forming a hollow square ring.
+            Top-left origin (0,0), x→, y↓:
+
+              x=0 1 2 3 4
+            y=0   A A A
+            y=1 B       C
+            y=2 B       C
+            y=3 B       C
+            y=4   D D D
+
+            Rectangles (width × height):
+              A: (1,0,3,1)  // top edge
+              B: (0,1,1,3)  // left edge
+              C: (4,1,1,3)  // right edge
+              D: (1,4,3,1)  // bottom edge
+
+            They only touch corners or edges, with no overlapping areas.
+            The expected result is exactly these four rectangles, unmerged.
+        */
+
+        List<Rectangle> rectangles = new ()
+        {
+            new (1, 0, 3, 1), // A
+            new (0, 1, 1, 3), // B
+            new (4, 1, 1, 3), // C
+            new (1, 4, 3, 1) // D
+        };
+
+        List<Rectangle> merged = Region.MergeRectangles (rectangles, false);
+
+        // Because there's no overlapping area, the method shouldn't merge any of them.
+        Assert.Equal (4, merged.Count);
+        Assert.Contains (new (1, 0, 3, 1), merged);
+        Assert.Contains (new (0, 1, 1, 3), merged);
+        Assert.Contains (new (4, 1, 1, 3), merged);
+        Assert.Contains (new (1, 4, 3, 1), merged);
+    }
+
+    [Fact]
+    public void MergeRectangles_ComplexContainedRectangles_AllMergeIntoBoundingRect ()
+    {
+        /*
+        INPUT: (top-left origin, x→, y↓):
+
+           x=0 1 2 3 4 5
+        y=0  A A A A A A
+        y=1  A . . . . A
+        y=2  A . B B . A
+        y=3  A . B B . A
+        y=4  A . . . C C
+        y=5  A A A A C C
+
+        Where:
+          A = (0,0,6,6)  // Large bounding rectangle
+          B = (2,2,2,2)  // Fully contained inside A
+          C = (4,4,2,2)  // Also fully contained inside A
+     */
+
+        List<Rectangle> rectangles = new ()
+        {
+            new (0, 0, 6, 6), // A
+            new (2, 2, 2, 2), // B inside A
+            new (4, 4, 2, 2) // C inside A
+        };
+
+        List<Rectangle> merged = Region.MergeRectangles (rectangles, minimize: false);
+
+        /*
+           OUTPUT: The expected result should be a minimal set of non-overlapping rectangles
+           that cover the same area as the input rectangles.
+
+            x=0 1 2 3 4 5
+         y=0  a a b b c c
+         y=1  a a b b c c
+         y=2  a a b b c c
+         y=3  a a b b c c
+         y=4  a a b b c c
+         y=5  a a b b c c
+
+       */
+
+        Assert.Equal (3, merged.Count);
+        Assert.Contains (new (0, 0, 2, 6), merged); // a
+        Assert.Contains (new (2, 0, 2, 6), merged); // b
+        Assert.Contains (new (4, 0, 2, 6), merged); // c
+    }
+
+    [Fact]
+    public void MergeRectangles_ComplexOverlap_ReturnsMergedRectangles ()
+    {
+        /*
+            INPUT: Visual diagram treating (0,0) as top-left, x increasing to the right, y increasing downward:
+
+                  x=0 1 2 3 4 5 6 ...
+              y=0   A A
+              y=1   A B B
+              y=2     B B
+              y=3         C C
+              y=4         C D D
+              y=5           D D
+
+            A overlaps B slightly; C overlaps D slightly. The union of A & B forms one rectangle,
+            and the union of C & D forms another.
+        */
+
+        List<Rectangle> rectangles = new ()
+        {
+            // A
+            new (0, 0, 2, 2),
+
+            // B
+            new (1, 1, 2, 2),
+
+            // C
+            new (3, 3, 2, 2),
+
+            // D
+            new (4, 4, 2, 2)
+        };
+
+        List<Rectangle> merged = Region.MergeRectangles (rectangles, false);
+
+        /*
+           OUTPUT:  Merged fragments (top-left origin, x→, y↓).
+           Lowercase letters a..f show the six sub-rectangles:
+
+              x=0 1 2 3 4 5
+           y=0  a b
+           y=1  a b c
+           y=2    b c
+           y=3        d e
+           y=4        d e f
+           y=5          e f
+        */
+
+        Assert.Equal (6, merged.Count);
+
+        Assert.Contains (new (0, 0, 1, 2), merged); // a
+        Assert.Contains (new (1, 0, 1, 3), merged); // b
+        Assert.Contains (new (2, 1, 1, 2), merged); // c
+        Assert.Contains (new (3, 3, 1, 2), merged); // d
+        Assert.Contains (new (4, 3, 1, 3), merged); // e
+        Assert.Contains (new (5, 4, 1, 2), merged); // f
+    }
+
+    [Fact]
+    public void MergeRectangles_NoOverlap_ReturnsSameRectangles ()
+    {
+        List<Rectangle> rectangles = new ()
+        {
+            new (0, 0, 10, 10),
+            new (20, 20, 10, 10),
+            new (40, 40, 10, 10)
+        };
+
+        List<Rectangle> result = Region.MergeRectangles (rectangles, false);
+
+        Assert.Equal (3, result.Count);
+        Assert.Contains (new (0, 0, 10, 10), result);
+        Assert.Contains (new (20, 20, 10, 10), result);
+        Assert.Contains (new (40, 40, 10, 10), result);
+    }
+
+
+    [Fact]
+    public void MergeRectangles_EmptyRectangles_ReturnsEmptyList ()
+    {
+        // Arrange: Create list of empty rectangles
+        var emptyRectangles = new List<Rectangle> { new (0, 0, 0, 0), new (0, 0, 0, 0) };
+
+        // Act: Call MergeRectangles with granular output
+        var result = Region.MergeRectangles (emptyRectangles, minimize: false);
+
+        // Assert: Result is empty
+        Assert.Empty (result);
+    }
+
+}

+ 893 - 0
UnitTests/Drawing/Region/RegionTests.cs

@@ -0,0 +1,893 @@
+namespace Terminal.Gui.DrawingTests;
+
+public class RegionTests
+{
+    [Fact]
+    public void Clone_CreatesExactCopy ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        Region clone = region.Clone ();
+        Assert.True (clone.Contains (20, 20));
+        Assert.Equal (region.GetRectangles (), clone.GetRectangles ());
+    }
+
+    [Fact]
+    public void Combine_EmptyRectangles_ProducesEmptyRegion ()
+    {
+        // Arrange: Create region and combine with empty rectangles
+        var region = new Region ();
+        region.Combine (new Rectangle (0, 0, 0, 0), RegionOp.Union); // Empty rectangle
+        region.Combine (new Region (), RegionOp.Union); // Empty region
+
+        // Assert: Region is empty
+        Assert.Empty (region.GetRectangles ());
+    }
+
+    [Fact]
+    public void Complement_Rectangle_ComplementsRegion ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        region.Complement (new (0, 0, 100, 100));
+        Assert.True (region.Contains (5, 5));
+        Assert.False (region.Contains (20, 20));
+    }
+
+    [Theory]
+    [MemberData (nameof (Complement_TestData))]
+    public void Complement_Region_Success (Region region, Rectangle [] rectangles, Rectangle [] expectedScans)
+    {
+        foreach (Rectangle rect in rectangles)
+        {
+            region.Complement (rect);
+        }
+
+        Rectangle [] actualScans = region.GetRectangles ();
+        Assert.Equal (expectedScans, actualScans);
+    }
+
+
+    public static IEnumerable<object []> Complement_TestData ()
+    {
+        yield return new object []
+        {
+            new Region (new (10, 10, 100, 100)),
+            new Rectangle [] { new (40, 60, 100, 20) },
+            new Rectangle [] { new (110, 60, 30, 20) }
+        };
+
+        yield return new object []
+        {
+            new Region (new (70, 10, 100, 100)),
+            new Rectangle [] { new (40, 60, 100, 20) },
+            new Rectangle [] { new (40, 60, 30, 20) }
+        };
+
+        yield return new object []
+        {
+            new Region (new (40, 100, 100, 100)),
+            new Rectangle [] { new (70, 80, 50, 40) },
+            new Rectangle [] { new (70, 80, 50, 20) }
+        };
+
+        yield return new object []
+        {
+            new Region (new (40, 10, 100, 100)),
+            new Rectangle [] { new (70, 80, 50, 40) },
+            new Rectangle [] { new (70, 110, 50, 10) }
+        };
+
+        yield return new object []
+        {
+            new Region (new (30, 30, 80, 80)),
+            new Rectangle []
+            {
+                new (45, 45, 200, 200),
+                new (160, 260, 10, 10),
+                new (170, 260, 10, 10)
+            },
+            new Rectangle [] { new (170, 260, 10, 10) }
+        };
+
+        yield return new object []
+        {
+            new Region (),
+            new [] { Rectangle.Empty },
+            new Rectangle[0]
+        };
+
+        yield return new object []
+        {
+            new Region (),
+            new Rectangle [] { new (1, 2, 3, 4) },
+            new Rectangle[0]
+        };
+    }
+
+    [Fact]
+    public void Complement_WithRectangle_ComplementsRegion ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        var rect = new Rectangle (0, 0, 100, 100);
+
+        region.Complement (rect);
+
+        // Points that were inside the original region should now be outside
+        Assert.False (region.Contains (35, 35));
+
+        // Points that were outside the original region but inside bounds should now be inside
+        Assert.True (region.Contains (5, 5));
+        Assert.True (region.Contains (95, 95));
+    }
+
+    [Fact]
+    public void Complement_WithRegion_ComplementsRegion ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        var bounds = new Rectangle (0, 0, 100, 100);
+
+        region.Complement (bounds);
+
+        // Points that were inside the original region should now be outside
+        Assert.False (region.Contains (35, 35));
+
+        // Points that were outside the original region but inside bounds should now be inside
+        Assert.True (region.Contains (5, 5));
+        Assert.True (region.Contains (95, 95));
+    }
+
+    [Fact]
+    public void Constructor_EmptyRegion_IsEmpty ()
+    {
+        var region = new Region ();
+        Assert.True (region.IsEmpty ());
+    }
+
+    [Fact]
+    public void Constructor_WithRectangle_IsNotEmpty ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        Assert.False (region.IsEmpty ());
+    }
+
+    [Fact]
+    public void Contains_Point_ReturnsCorrectResult ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+
+        Assert.True (region.Contains (20, 20));
+        Assert.False (region.Contains (100, 100));
+    }
+
+    [Fact]
+    public void Contains_PointInsideRegion_ReturnsTrue ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        Assert.True (region.Contains (20, 20));
+    }
+
+    [Fact]
+    public void Contains_RectangleInsideRegion_ReturnsTrue ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        Assert.True (region.Contains (new (20, 20, 10, 10)));
+    }
+
+    [Fact]
+    public void Equals_NullRegion_ReturnsFalse ()
+    {
+        var region = new Region ();
+        Assert.False (region.Equals (null));
+    }
+
+    [Fact]
+    public void Equals_SameRegion_ReturnsTrue ()
+    {
+        var region = new Region (new (1, 2, 3, 4));
+        Assert.True (region.Equals (region));
+    }
+
+    public static IEnumerable<object []> Equals_TestData ()
+    {
+        static Region Empty ()
+        {
+            Region emptyRegion = new ();
+            emptyRegion.Intersect (Rectangle.Empty);
+
+            return emptyRegion;
+        }
+
+        yield return new object [] { new Region (), new Region (), true };
+        yield return new object [] { new Region (), Empty (), true };
+        yield return new object [] { new Region (), new Region (new (1, 2, 3, 4)), false };
+
+        yield return new object [] { Empty (), Empty (), true };
+        yield return new object [] { Empty (), new Region (new (0, 0, 0, 0)), true };
+        yield return new object [] { Empty (), new Region (new (1, 2, 3, 3)), false };
+
+        yield return new object [] { new Region (new (1, 2, 3, 4)), new Region (new (1, 2, 3, 4)), true };
+        yield return new object [] { new Region (new (1, 2, 3, 4)), new Region (new (2, 2, 3, 4)), false };
+        yield return new object [] { new Region (new (1, 2, 3, 4)), new Region (new (1, 3, 3, 4)), false };
+        yield return new object [] { new Region (new (1, 2, 3, 4)), new Region (new (1, 2, 4, 4)), false };
+        yield return new object [] { new Region (new (1, 2, 3, 4)), new Region (new (1, 2, 3, 5)), false };
+    }
+
+    [Theory]
+    [MemberData (nameof (Equals_TestData))]
+    public void Equals_Valid_ReturnsExpected (Region region1, Region region2, bool expected) { Assert.Equal (expected, region1.Equals (region2)); }
+
+    [Fact]
+    public void GetBounds_ReturnsBoundingRectangle ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        region.Union (new Rectangle (100, 100, 20, 20));
+        Rectangle bounds = region.GetBounds ();
+        Assert.Equal (new (10, 10, 110, 110), bounds);
+    }
+
+    [Fact]
+    public void GetBounds_ReturnsCorrectBounds ()
+    {
+        var region = new Region ();
+        region.Union (new Rectangle (10, 10, 50, 50));
+        region.Union (new Rectangle (30, 30, 50, 50));
+
+        Rectangle bounds = region.GetBounds ();
+
+        Assert.Equal (new (10, 10, 70, 70), bounds);
+    }
+
+    [Fact]
+    public void GetRegionScans_ReturnsAllRectangles ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        region.Union (new Rectangle (100, 100, 20, 20));
+        Rectangle [] scans = region.GetRectangles ();
+        Assert.Equal (2, scans.Length);
+        Assert.Contains (new (10, 10, 50, 50), scans);
+        Assert.Contains (new (100, 100, 20, 20), scans);
+    }
+
+    [Fact]
+    public void Intersect_Rectangle_IntersectsRegion ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        region.Intersect (new Rectangle (30, 30, 50, 50));
+        Assert.False (region.Contains (20, 20));
+        Assert.True (region.Contains (40, 40));
+    }
+
+    [Fact]
+    public void Intersect_Region_IntersectsRegions ()
+    {
+        var region1 = new Region (new (10, 10, 50, 50));
+        var region2 = new Region (new (30, 30, 50, 50));
+        region1.Intersect (region2);
+        Assert.False (region1.Contains (20, 20));
+        Assert.True (region1.Contains (40, 40));
+    }
+
+    [Fact]
+    public void Intersect_WithEmptyRectangle_ResultsInEmptyRegion ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 10, 10));
+        var rectangle = Rectangle.Empty; // Use Empty instead of 0-size
+
+        // Act
+        region.Intersect (rectangle);
+
+        // Assert
+        Assert.True (region.IsEmpty ());
+    }
+
+    [Theory]
+    [InlineData (0, 0, 0, 0)] // Empty by zero size
+    [InlineData (0, 0, 0, 10)] // Empty by zero width
+    [InlineData (0, 0, 10, 0)] // Empty by zero height
+    [InlineData (-5, -5, 0, 0)] // Empty by zero size at negative coords
+    [InlineData (10, 10, -5, -5)] // Empty by negative size
+    public void Intersect_WithEmptyRegion_ResultsInEmptyRegion (int x, int y, int width, int height)
+    {
+        // Arrange
+        var region = new Region ();
+        region.Union (new Rectangle (0, 0, 10, 10));
+        region.Union (new Rectangle (20, 0, 10, 10));
+
+        // Create a region that should be considered empty
+        var emptyRegion = new Region ();
+
+        if (width <= 0 || height <= 0)
+        {
+            // For negative or zero dimensions, use an empty region
+            emptyRegion = new ();
+        }
+        else
+        {
+            emptyRegion = new (new (x, y, width, height));
+        }
+
+        // Verify initial states
+        Assert.Equal (2, region.GetRectangles ().Length);
+        Assert.True (emptyRegion.IsEmpty ());
+
+        // Act
+        region.Intersect (emptyRegion);
+
+        // Assert
+        Assert.True (region.IsEmpty ());
+        Assert.Empty (region.GetRectangles ());
+    }
+
+    [Fact]
+    public void Intersect_WithFullyContainedRectangle_ResultsInSmallerRegion ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 10, 10));
+        var rectangle = new Rectangle (2, 2, 4, 4);
+
+        // Act
+        region.Intersect (rectangle);
+
+        // Assert
+        Assert.Single (region.GetRectangles ());
+        Assert.Equal (new (2, 2, 4, 4), region.GetRectangles () [0]);
+    }
+
+    [Fact]
+    public void Intersect_WithMultipleRectanglesInRegion_IntersectsAll ()
+    {
+        // Arrange
+        var region = new Region ();
+        region.Union (new Rectangle (0, 0, 5, 5));
+        region.Union (new Rectangle (10, 0, 5, 5));
+        var rectangle = new Rectangle (2, 2, 10, 2);
+
+        // Act
+        region.Intersect (rectangle);
+
+        // Assert
+        Assert.Equal (2, region.GetRectangles ().Length);
+        Assert.Contains (new (2, 2, 3, 2), region.GetRectangles ());
+        Assert.Contains (new (10, 2, 2, 2), region.GetRectangles ());
+    }
+
+    //[Fact]
+    //public void Intersect_WithEmptyRegion_ResultsInEmptyRegion ()
+    //{
+    //    // Arrange
+    //    var region = new Region ();
+    //    var rectangle = new Rectangle (0, 0, 10, 10);
+
+    //    // Act
+    //    region.Intersect (rectangle);
+
+    //    // Assert
+    //    Assert.True (region.IsEmpty ());
+    //}
+
+    [Fact]
+    public void Intersect_WithNonOverlappingRectangle_ResultsInEmptyRegion ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 5, 5));
+        var rectangle = new Rectangle (10, 10, 5, 5);
+
+        // Act
+        region.Intersect (rectangle);
+
+        // Assert
+        Assert.True (region.IsEmpty ());
+    }
+
+    [Fact]
+    public void Intersect_WithNullRegion_ResultsInEmptyRegion ()
+    {
+        // Arrange
+        var region = new Region ();
+        region.Union (new Rectangle (0, 0, 10, 10));
+        region.Union (new Rectangle (20, 0, 10, 10));
+
+        // Verify initial state
+        Assert.Equal (2, region.GetRectangles ().Length);
+
+        // Act
+        region.Intersect (null);
+
+        // Assert
+        Assert.True (region.IsEmpty ());
+        Assert.Empty (region.GetRectangles ());
+    }
+
+    [Fact]
+    public void Intersect_WithPartiallyOverlappingRectangle_ResultsInIntersectedRegion ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 5, 5));
+        var rectangle = new Rectangle (2, 2, 5, 5);
+
+        // Act
+        region.Intersect (rectangle);
+
+        // Assert
+        Assert.Single (region.GetRectangles ());
+        Assert.Equal (new (2, 2, 3, 3), region.GetRectangles () [0]);
+    }
+
+    [Fact]
+    public void Intersect_WithRectangle_IntersectsRectangles ()
+    {
+        var region = new Region (new (10, 10, 50, 50));
+        var rect = new Rectangle (30, 30, 50, 50);
+
+        region.Intersect (rect);
+
+        Assert.True (region.Contains (35, 35));
+        Assert.False (region.Contains (20, 20));
+    }
+
+    [Fact]
+    public void Intersect_WithRegion_IntersectsRegions ()
+    {
+        var region1 = new Region (new (10, 10, 50, 50));
+        var region2 = new Region (new (30, 30, 50, 50));
+
+        region1.Intersect (region2.GetBounds ());
+
+        Assert.True (region1.Contains (35, 35));
+        Assert.False (region1.Contains (20, 20));
+    }
+
+    [Fact]
+    public void Intersect_ImmediateNormalization_AffectsRectangleOrder ()
+    {
+        // Create a region with two overlapping rectangles
+        var region1 = new Region (new (0, 0, 4, 4)); // 0,0 to 4,4
+
+        // Intersect with a region that partially overlaps
+        var region2 = new Region (new (2, 2, 4, 4)); // 2,2 to 6,6
+        region1.Intersect (region2);
+
+        // Get the resulting rectangles
+        Rectangle [] result = region1.GetRectangles ();
+
+        // Expected behavior from original Region: 
+        // Intersect immediately produces a single rectangle (2,2,2,2)
+        Assert.Single (result); // Original has 1 rectangle due to immediate processing
+        Assert.Equal (new (2, 2, 2, 2), result [0]);
+
+        // My updated Region defers normalization after Intersect, 
+        // so GetRectangles() might merge differently or preserve order differently,
+        // potentially failing the exact match or count due to _isDirty
+    }
+
+    [Fact]
+    public void IsEmpty_AfterClear_ReturnsTrue ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 10, 10));
+
+        // Act
+        region.Intersect (Rectangle.Empty);
+
+        // Assert
+        Assert.True (region.IsEmpty ());
+        Assert.Empty (region.GetRectangles ());
+    }
+
+    [Fact]
+    public void IsEmpty_AfterComplement_ReturnsCorrectState ()
+    {
+        // Test 1: Complement a region with bounds that fully contain it
+        var region = new Region (new (2, 2, 5, 5)); // Small inner rectangle
+        region.Complement (new (0, 0, 10, 10)); // Larger outer bounds
+        Assert.False (region.IsEmpty ()); // Should have area around the original rectangle
+
+        // Test 2: Complement with bounds equal to the region
+        region = new (new (0, 0, 10, 10));
+        region.Complement (new (0, 0, 10, 10));
+        Assert.True (region.IsEmpty ()); // Should be empty as there's no area left
+
+        // Test 3: Complement with empty bounds
+        region = new (new (0, 0, 10, 10));
+        region.Complement (Rectangle.Empty);
+        Assert.True (region.IsEmpty ()); // Should be empty as there's no bounds
+    }
+
+    [Fact]
+    public void IsEmpty_AfterExclude_ReturnsTrue ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 10, 10));
+
+        // Act
+        region.Exclude (new Rectangle (0, 0, 10, 10));
+
+        // Assert
+        Assert.True (region.IsEmpty ());
+        Assert.Empty (region.GetRectangles ());
+    }
+
+    [Fact]
+    public void IsEmpty_AfterUnion_ReturnsFalse ()
+    {
+        // Arrange
+        var region = new Region ();
+        region.Union (new Rectangle (0, 0, 10, 10));
+
+        // Assert
+        Assert.False (region.IsEmpty ());
+        Assert.Single (region.GetRectangles ());
+    }
+
+    [Fact]
+    public void IsEmpty_EmptyRegion_ReturnsTrue ()
+    {
+        var region = new Region ();
+        Assert.True (region.IsEmpty ());
+    }
+
+    [Fact]
+    public void IsEmpty_MultipleOperations_ReturnsExpectedResult ()
+    {
+        // Arrange
+        var region = new Region ();
+
+        // Act & Assert - Should be empty initially
+        Assert.True (region.IsEmpty ());
+
+        // Add a rectangle - Should not be empty
+        region.Union (new Rectangle (0, 0, 10, 10));
+        Assert.False (region.IsEmpty ());
+
+        // Exclude the same rectangle - Should be empty again
+        region.Exclude (new Rectangle (0, 0, 10, 10));
+        Assert.True (region.IsEmpty ());
+
+        // Add two rectangles - Should not be empty
+        region.Union (new Rectangle (0, 0, 5, 5));
+        region.Union (new Rectangle (10, 10, 5, 5));
+        Assert.False (region.IsEmpty ());
+    }
+
+    [Fact]
+    public void IsEmpty_NewRegion_ReturnsTrue ()
+    {
+        // Arrange
+        var region = new Region ();
+
+        // Act & Assert
+        Assert.True (region.IsEmpty ());
+        Assert.Empty (region.GetRectangles ());
+    }
+
+    [Fact]
+    public void IsEmpty_ReturnsCorrectResult ()
+    {
+        var region = new Region ();
+
+        Assert.True (region.IsEmpty ());
+
+        region.Union (new Rectangle (10, 10, 50, 50));
+
+        Assert.False (region.IsEmpty ());
+    }
+
+    [Theory]
+    [InlineData (0, 0, 1, 1)] // 1x1 at origin
+    [InlineData (10, 10, 5, 5)] // 5x5 at (10,10)
+    [InlineData (-5, -5, 10, 10)] // Negative coordinates
+    public void IsEmpty_ValidRectangle_ReturnsFalse (int x, int y, int width, int height)
+    {
+        // Arrange
+        var region = new Region (new (x, y, width, height));
+
+        // Assert
+        Assert.False (region.IsEmpty ());
+        Assert.Single (region.GetRectangles ());
+    }
+
+    [Theory]
+    [InlineData (0, 0, 0, 0)] // Zero size
+    [InlineData (0, 0, 0, 10)] // Zero width
+    [InlineData (0, 0, 10, 0)] // Zero height
+    [InlineData (-5, -5, 0, 0)] // Zero size at negative coords
+    public void IsEmpty_ZeroSizeRectangle_ReturnsCorrectState (int x, int y, int width, int height)
+    {
+        var region = new Region (new (x, y, width, height));
+
+        // Only check IsEmpty() since Rectangle(0,0,0,0) is still stored
+        Assert.True (region.IsEmpty ());
+    }
+
+    //[Fact]
+    //public void MinimalUnion_SingleRectangle_DoesNotChange ()
+    //{
+    //    var region = new Region (new Rectangle (0, 0, 10, 10));
+    //    region.MinimalUnion (new Rectangle (0, 0, 10, 10));
+
+    //    Assert.Single (region.GetRectangles ());
+    //    Assert.Equal (new Rectangle (0, 0, 10, 10), region.GetRectangles ().First ());
+    //}
+
+    //[Fact]
+    //public void MinimalUnion_OverlappingRectangles_MergesIntoOne ()
+    //{
+    //    var region = new Region (new Rectangle (0, 0, 10, 10));
+    //    region.MinimalUnion (new Rectangle (5, 0, 10, 10));
+
+    //    Assert.Single (region.GetRectangles ());
+    //    Assert.Equal (new Rectangle (0, 0, 15, 10), region.GetRectangles ().First ());
+    //}
+
+    //[Fact]
+    //public void MinimalUnion_AdjacentRectangles_MergesIntoOne ()
+    //{
+    //    var region = new Region (new Rectangle (0, 0, 10, 10));
+    //    region.MinimalUnion (new Rectangle (10, 0, 10, 10));
+
+    //    Assert.Single (region.GetRectangles ());
+    //    Assert.Equal (new Rectangle (0, 0, 20, 10), region.GetRectangles ().First ());
+    //}
+
+    //[Fact]
+    //public void MinimalUnion_SeparateRectangles_KeepsBoth ()
+    //{
+    //    var region = new Region (new Rectangle (0, 0, 10, 10));
+    //    region.MinimalUnion (new Rectangle (20, 20, 10, 10));
+
+    //    Assert.Equal (2, region.GetRectangles ().Length);
+    //    Assert.Contains (new Rectangle (0, 0, 10, 10), region.GetRectangles ());
+    //    Assert.Contains (new Rectangle (20, 20, 10, 10), region.GetRectangles ());
+    //}
+
+    //[Fact]
+    //public void MinimalUnion_ComplexMerging_ProducesMinimalSet ()
+    //{
+    //    var region = new Region (new Rectangle (0, 0, 10, 10));
+    //    region.MinimalUnion (new Rectangle (10, 0, 10, 10));
+    //    region.MinimalUnion (new Rectangle (0, 10, 10, 10));
+    //    region.MinimalUnion (new Rectangle (10, 10, 10, 10));
+
+    //    Assert.Single (region.GetRectangles ());
+    //    Assert.Equal (new Rectangle (0, 0, 20, 20), region.GetRectangles ().First ());
+    //}
+
+
+    [Fact]
+    public void Intersect_ViewportLimitsDrawnRegion ()
+    {
+        // Arrange: Create regions for viewport and drawn content
+        var viewport = new Region (new Rectangle (0, 0, 100, 100));        // Viewport
+        var drawnRegion = new Region (new Rectangle (50, 50, 200, 200));    // Larger drawn content
+
+        // Act: Intersect drawn region with viewport
+        drawnRegion.Intersect (viewport);
+
+        // Assert: Drawn region should be limited to viewport
+        var rectangles = drawnRegion.GetRectangles ();
+        Assert.Single (rectangles);
+        Assert.Equal (new Rectangle (50, 50, 50, 50), rectangles [0]);      // Limited to viewport bounds
+    }
+
+    //[Fact]
+
+
+    //public void MinimalUnion_HorizontalMerge_MergesToSingleRectangle ()
+    //{
+    //    // Arrange: Create a region with a rectangle at (0,0,5,5)
+    //    var region = new Region (new Rectangle (0, 0, 5, 5));
+
+    //    // Act: Merge an adjacent rectangle on the right using MinimalUnion
+    //    region.MinimalUnion (new Rectangle (5, 0, 5, 5));
+    //    var result = region.GetRectangles ();
+
+    //    // Assert: Expect a single merged rectangle covering (0,0,10,5)
+    //    Assert.Single (result);
+    //    Assert.Equal (new Rectangle (0, 0, 10, 5), result [0]);
+    //}
+
+    //[Fact]
+    //public void MinimalUnion_VerticalMerge_MergesToSingleRectangle ()
+    //{
+    //    // Arrange: Create a region with a rectangle at (0,0,5,5)
+    //    var region = new Region (new Rectangle (0, 0, 5, 5));
+
+    //    // Act: Merge an adjacent rectangle below using MinimalUnion
+    //    region.MinimalUnion (new Rectangle (0, 5, 5, 5));
+    //    var result = region.GetRectangles ();
+
+    //    // Assert: Expect a single merged rectangle covering (0,0,5,10)
+    //    Assert.Single (result);
+    //    Assert.Equal (new Rectangle (0, 0, 5, 10), result [0]);
+    //}
+
+    //[Fact]
+    //public void MinimalUnion_OverlappingMerge_MergesToSingleRectangle ()
+    //{
+    //    // Arrange: Create a region with a rectangle that overlaps with the next one horizontally
+    //    var region = new Region (new Rectangle (0, 0, 6, 5));
+
+    //    // Act: Merge an overlapping rectangle using MinimalUnion
+    //    region.MinimalUnion (new Rectangle (4, 0, 6, 5));
+    //    var result = region.GetRectangles ();
+
+    //    // Assert: Expect a single merged rectangle covering (0,0,10,5)
+    //    Assert.Single (result);
+    //    Assert.Equal (new Rectangle (0, 0, 10, 5), result [0]);
+    //}
+
+    //[Fact]
+    //public void MinimalUnion_NonAdjacentRectangles_NoMergeOccurs ()
+    //{
+    //    // Arrange: Create a region with one rectangle
+    //    var region = new Region (new Rectangle (0, 0, 5, 5));
+
+    //    // Act: Merge with a rectangle that does not touch the first
+    //    region.MinimalUnion (new Rectangle (6, 0, 5, 5));
+    //    var result = region.GetRectangles ();
+
+    //    // Assert: Expect two separate rectangles since they are not adjacent
+    //    Assert.Equal (2, result.Length);
+    //    Assert.Contains (new Rectangle (0, 0, 5, 5), result);
+    //    Assert.Contains (new Rectangle (6, 0, 5, 5), result);
+    //}
+
+    //[Fact]
+    //public void MinimalUnion_MultipleMerge_FormsSingleContiguousRectangle ()
+    //{
+    //    // Arrange: Four small rectangles that form a contiguous 6x6 block
+    //    var region = new Region (new Rectangle (0, 0, 3, 3));
+
+    //    // Act: Merge adjacent rectangles one by one using MinimalUnion
+    //    region.MinimalUnion (new Rectangle (3, 0, 3, 3)); // Now covers (0,0,6,3)
+    //    region.MinimalUnion (new Rectangle (0, 3, 3, 3)); // Add bottom-left
+    //    region.MinimalUnion (new Rectangle (3, 3, 3, 3)); // Add bottom-right to complete block
+    //    var result = region.GetRectangles ();
+
+    //    // Assert: Expect a single merged rectangle covering (0,0,6,6)
+    //    Assert.Single (result);
+    //    Assert.Equal (new Rectangle (0, 0, 6, 6), result [0]);
+    //}
+
+    [Fact]
+    public void Translate_EmptyRegionAfterEmptyCombine_NoEffect ()
+    {
+        // Arrange: Create region and combine with empty rectangles
+        var region = new Region ();
+        region.Combine (new Rectangle (0, 0, 0, 0), RegionOp.Union); // Empty rectangle
+        region.Combine (new Region (), RegionOp.Union); // Empty region
+
+        // Act: Translate by (10, 20)
+        region.Translate (10, 20);
+
+        // Assert: Still empty
+        Assert.Empty (region.GetRectangles ());
+    }
+
+    [Fact]
+    public void Union_Rectangle_AddsToRegion ()
+    {
+        var region = new Region ();
+        region.Union (new Rectangle (10, 10, 50, 50));
+        Assert.False (region.IsEmpty ());
+        Assert.True (region.Contains (20, 20));
+    }
+
+    [Fact]
+    public void Union_Region_MergesRegions ()
+    {
+        var region1 = new Region (new (10, 10, 50, 50));
+        var region2 = new Region (new (30, 30, 50, 50));
+        region1.Union (region2);
+        Assert.True (region1.Contains (20, 20));
+        Assert.True (region1.Contains (40, 40));
+    }
+
+    /// <summary>
+    ///     Proves MergeRegion does not overly combine regions.
+    /// </summary>
+    [Fact]
+    public void Union_Region_MergesRegions_NonOverlapping ()
+    {
+        //  012345
+        // 0+++
+        // 1+ + 
+        // 2+++
+        // 3   ***
+        // 4   * *
+        // 5   ***
+
+        var region1 = new Region (new (0, 0, 3, 3));
+        var region2 = new Region (new (3, 3, 3, 3));
+        region1.Union (region2);
+
+        // Positive
+        Assert.True (region1.Contains (0, 0));
+        Assert.True (region1.Contains (1, 1));
+        Assert.True (region1.Contains (2, 2));
+        Assert.True (region1.Contains (4, 4));
+        Assert.True (region1.Contains (5, 5));
+
+        // Negative
+        Assert.False (region1.Contains (0, 3));
+        Assert.False (region1.Contains (3, 0));
+        Assert.False (region1.Contains (6, 6));
+    }
+
+    /// <summary>
+    ///     Proves MergeRegion does not overly combine regions.
+    /// </summary>
+    [Fact]
+    public void Union_Region_MergesRegions_Overlapping ()
+    {
+        //  01234567
+        // 0+++++
+        // 1+   +
+        // 2+   +
+        // 3+  *****
+        // 4+++*   *
+        // 5   *   *
+        // 6   *   *
+        // 7   *****
+
+        var region1 = new Region (new (0, 0, 5, 5));
+        var region2 = new Region (new (3, 3, 5, 5));
+        region1.Union (region2);
+
+        // Positive
+        Assert.True (region1.Contains (0, 0));
+        Assert.True (region1.Contains (1, 1));
+        Assert.True (region1.Contains (4, 4));
+        Assert.True (region1.Contains (7, 7));
+
+        // Negative
+        Assert.False (region1.Contains (0, 5));
+        Assert.False (region1.Contains (5, 0));
+        Assert.False (region1.Contains (8, 8));
+        Assert.False (region1.Contains (8, 8));
+    }
+
+    [Fact]
+    public void Union_WithRectangle_AddsRectangle ()
+    {
+        var region = new Region ();
+        var rect = new Rectangle (10, 10, 50, 50);
+
+        region.Union (rect);
+
+        Assert.True (region.Contains (20, 20));
+        Assert.False (region.Contains (100, 100));
+    }
+
+    [Fact]
+    public void Union_WithRegion_AddsRegion ()
+    {
+        var region1 = new Region (new (10, 10, 50, 50));
+        var region2 = new Region (new (30, 30, 50, 50));
+
+        region1.Union (region2.GetBounds ());
+
+        Assert.True (region1.Contains (20, 20));
+        Assert.True (region1.Contains (40, 40));
+    }
+
+    [Fact]
+    public void Intersect_DeferredNormalization_PreservesSegments ()
+    {
+        var region = new Region (new (0, 0, 3, 1)); // Horizontal
+        region.Union (new Rectangle (1, 0, 1, 2)); // Vertical
+        region.Intersect (new Rectangle (0, 0, 2, 2)); // Clip
+
+        Rectangle [] result = region.GetRectangles ();
+
+        // Original & Updated (with normalization disabled) behavior:
+        // Produces [(0,0,1,1), (1,0,1,2), (2,0,0,1)]
+        Assert.Equal (3, result.Length);
+        Assert.Contains (new (0, 0, 1, 1), result);
+        Assert.Contains (new (1, 0, 1, 2), result);
+        Assert.Contains (new (2, 0, 0, 1), result);
+    }
+
+
+
+}

+ 264 - 0
UnitTests/Drawing/Region/SubtractRectangleTests.cs

@@ -0,0 +1,264 @@
+namespace Terminal.Gui.DrawingTests;
+
+using Xunit;
+
+public class SubtractRectangleTests
+{
+    [Fact]
+    public void SubtractRectangle_NoOverlap_ReturnsOriginalRectangle ()
+    {
+        // Arrange: Non-overlapping rectangles
+        Rectangle original = new (0, 0, 10, 10);
+        Rectangle subtract = new (15, 15, 5, 5);
+
+        // Act: Subtract non-overlapping rectangle
+        var result = Region.SubtractRectangle (original, subtract).ToList ();
+
+        // Assert: Original rectangle unchanged
+        Assert.Single (result);
+        Assert.Equal (new Rectangle (0, 0, 10, 10), result [0]);
+    }
+
+    [Fact]
+    public void SubtractRectangle_CompleteOverlap_ReturnsEmpty ()
+    {
+        // Arrange: Subtract rectangle completely overlaps original
+        Rectangle original = new (0, 0, 5, 5);
+        Rectangle subtract = new (0, 0, 5, 5);
+
+        // Act: Subtract overlapping rectangle
+        var result = Region.SubtractRectangle (original, subtract).ToList ();
+
+        // Assert: No rectangles returned (empty result)
+        Assert.Empty (result);
+    }
+
+    [Fact]
+    public void SubtractRectangle_PartialOverlap_TopAndBottom ()
+    {
+        // Arrange: Original rectangle with subtract overlapping top and bottom
+        Rectangle original = new (0, 0, 10, 10);
+        Rectangle subtract = new (0, 4, 10, 2); // Overlaps y=4 to y=5
+
+        // Act: Subtract overlapping rectangle
+        var result = Region.SubtractRectangle (original, subtract).ToList ();
+
+        // Assert: Expect top (0,0,10,4) and bottom (0,6,10,4)
+        Assert.Equal (2, result.Count);
+        Assert.Contains (new Rectangle (0, 0, 10, 4), result); // Top part
+        Assert.Contains (new Rectangle (0, 6, 10, 4), result); // Bottom part
+    }
+
+    [Fact]
+    public void SubtractRectangle_PartialOverlap_LeftAndRight ()
+    {
+        // Arrange: Original rectangle with subtract overlapping left and right
+        Rectangle original = new (0, 0, 10, 10);
+        Rectangle subtract = new (4, 0, 2, 10); // Overlaps x=4 to x=5
+
+        // Act: Subtract overlapping rectangle
+        var result = Region.SubtractRectangle (original, subtract).ToList ();
+
+        // Assert: Expect left (0,0,4,10) and right (6,0,4,10)
+        Assert.Equal (2, result.Count);
+        Assert.Contains (new Rectangle (0, 0, 4, 10), result); // Left part
+        Assert.Contains (new Rectangle (6, 0, 4, 10), result); // Right part
+    }
+
+
+    [Fact]
+    public void SubtractRectangle_EmptyOriginal_ReturnsEmpty ()
+    {
+        // Arrange: Empty original rectangle
+        Rectangle original = Rectangle.Empty;
+        Rectangle subtract = new (0, 0, 5, 5);
+
+        // Act: Subtract from empty rectangle
+        var result = Region.SubtractRectangle (original, subtract).ToList ();
+
+        // Assert: No rectangles returned (empty result)
+        Assert.Empty (result);
+    }
+
+    [Fact]
+    public void SubtractRectangle_EmptySubtract_ReturnsOriginal ()
+    {
+        // Arrange: Empty subtract rectangle
+        Rectangle original = new (0, 0, 5, 5);
+        Rectangle subtract = Rectangle.Empty;
+
+        // Act: Subtract empty rectangle
+        var result = Region.SubtractRectangle (original, subtract).ToList ();
+
+        // Assert: Original rectangle unchanged
+        Assert.Single (result);
+        Assert.Equal (new Rectangle (0, 0, 5, 5), result [0]);
+    }
+
+
+    [Fact]
+    public void SubtractRectangle_ZeroWidthOrHeight_HandlesCorrectly ()
+    {
+        // Arrange: Rectangle with zero width or height
+        Rectangle original = new (0, 0, 5, 5);
+        Rectangle subtract = new (0, 0, 0, 5); // Zero width
+
+        // Act: Subtract zero-width rectangle
+        var result = Region.SubtractRectangle (original, subtract).ToList ();
+
+        // Assert: Original rectangle unchanged
+        Assert.Single (result);
+        Assert.Equal (new Rectangle (0, 0, 5, 5), result [0]);
+    }
+
+    [Fact]
+    public void SubtractRectangle_NegativeCoordinates_HandlesCorrectly ()
+    {
+        // Arrange:
+        // Original rectangle: (-5,-5) with width 10 and height 10.
+        // Subtract rectangle: (-3,-3) with width 2 and height 2.
+        Rectangle original = new (-5, -5, 10, 10);
+        Rectangle subtract = new (-3, -3, 2, 2);
+
+        // Act:
+        var results = Region.SubtractRectangle (original, subtract).ToList ();
+
+        // Expected fragments based on our algorithm:
+        // Top:    (-5,-5,10,2)
+        // Bottom: (-5,-1,10,6)
+        // Left:   (-5,-3,2,2)
+        // Right:  (-1,-3,6,2)
+        var expectedTop = new Rectangle (-5, -5, 10, 2);
+        var expectedBottom = new Rectangle (-5, -1, 10, 6);
+        var expectedLeft = new Rectangle (-5, -3, 2, 2);
+        var expectedRight = new Rectangle (-1, -3, 6, 2);
+
+        // Assert:
+        Assert.Contains (expectedTop, results);
+        Assert.Contains (expectedBottom, results);
+        Assert.Contains (expectedLeft, results);
+        Assert.Contains (expectedRight, results);
+        Assert.Equal (4, results.Count);
+    }
+
+    [Fact]
+    public void SubtractRectangle_EdgeOverlap_TopLeftCorner ()
+    {
+        // Arrange: Subtract overlaps only the top-left corner.
+        // Original: (0,0,5,5); Subtract: (0,0,1,1)
+        Rectangle original = new (0, 0, 5, 5);
+        Rectangle subtract = new (0, 0, 1, 1);
+
+        // Act:
+        var result = Region.SubtractRectangle (original, subtract).ToList ();
+
+        // The algorithm produces two fragments:
+        // 1. Bottom: (0,1,5,4)
+        // 2. Right:  (1,0,4,1)
+        Assert.Equal (2, result.Count);
+        Assert.Contains (new Rectangle (0, 1, 5, 4), result);
+        Assert.Contains (new Rectangle (1, 0, 4, 1), result);
+    }
+
+    // Updated L-shaped test: The algorithm produces 6 fragments, not 5.
+    [Fact]
+    public void SubtractRectangle_LShapedSubtract_MultipleFragments ()
+    {
+        // Arrange:
+        // Original: (0,0,6,6)
+        // subtract1: (2,2,2,1) creates a horizontal gap.
+        // subtract2: (2,3,1,1) removes an additional piece from the bottom fragment.
+        Rectangle original = new (0, 0, 6, 6);
+        Rectangle subtract1 = new (2, 2, 2, 1);
+        Rectangle subtract2 = new (2, 3, 1, 1);
+
+        // Act:
+        var intermediateResult = Region.SubtractRectangle (original, subtract1).ToList ();
+        var finalResult = intermediateResult.SelectMany (r => Region.SubtractRectangle (r, subtract2)).ToList ();
+
+        // Explanation:
+        // After subtracting subtract1, we expect these 4 fragments:
+        //   - Top:    (0,0,6,2)
+        //   - Bottom: (0,3,6,3)
+        //   - Left:   (0,2,2,1)
+        //   - Right:  (4,2,2,1)
+        // Then subtracting subtract2 from the bottom fragment (0,3,6,3) produces 3 fragments:
+        //   - Bottom part: (0,4,6,2)
+        //   - Left part:   (0,3,2,1)
+        //   - Right part:  (3,3,3,1)
+        // Total fragments = 1 (top) + 3 (modified bottom) + 1 (left) + 1 (right) = 6.
+        Assert.Equal (6, finalResult.Count);
+        Assert.Contains (new Rectangle (0, 0, 6, 2), finalResult);  // Top fragment remains unchanged.
+        Assert.Contains (new Rectangle (0, 4, 6, 2), finalResult);  // Bottom part from modified bottom.
+        Assert.Contains (new Rectangle (0, 3, 2, 1), finalResult);  // Left part from modified bottom.
+        Assert.Contains (new Rectangle (3, 3, 3, 1), finalResult);  // Right part from modified bottom.
+        Assert.Contains (new Rectangle (0, 2, 2, 1), finalResult);  // Left fragment from first subtraction.
+        Assert.Contains (new Rectangle (4, 2, 2, 1), finalResult);  // Right fragment from first subtraction.
+    }
+
+    // Additional focused tests for L-shaped subtraction scenarios:
+
+    [Fact]
+    public void SubtractRectangle_LShapedSubtract_HorizontalThenVertical ()
+    {
+        // Arrange:
+        // First, subtract a horizontal band.
+        Rectangle original = new (0, 0, 10, 10);
+        Rectangle subtractHorizontal = new (0, 4, 10, 2);
+        var horizontalResult = Region.SubtractRectangle (original, subtractHorizontal).ToList ();
+        // Expect two fragments: top (0,0,10,4) and bottom (0,6,10,4).
+        Assert.Equal (2, horizontalResult.Count);
+        Assert.Contains (new Rectangle (0, 0, 10, 4), horizontalResult);
+        Assert.Contains (new Rectangle (0, 6, 10, 4), horizontalResult);
+
+        // Act:
+        // Now, subtract a vertical piece from the top fragment.
+        Rectangle subtractVertical = new (3, 0, 2, 4);
+        var finalResult = Region.SubtractRectangle (horizontalResult [0], subtractVertical).ToList ();
+
+        // Assert:
+        // The subtraction yields two fragments:
+        // Left fragment: (0,0,3,4)
+        // Right fragment: (5,0,5,4)
+        Assert.Equal (2, finalResult.Count);
+        Assert.Contains (new Rectangle (0, 0, 3, 4), finalResult);
+        Assert.Contains (new Rectangle (5, 0, 5, 4), finalResult);
+    }
+
+    [Fact]
+    public void SubtractRectangle_LShapedSubtract_VerticalThenHorizontal ()
+    {
+        // Arrange:
+        // First, subtract a vertical band.
+        // Original: (0,0,10,10)
+        // Vertical subtract: (4,0,2,10) produces two fragments:
+        //   Left: (0,0,4,10) and Right: (6,0,4,10)
+        Rectangle original = new (0, 0, 10, 10);
+        Rectangle subtractVertical = new (4, 0, 2, 10);
+        var verticalResult = Region.SubtractRectangle (original, subtractVertical).ToList ();
+        Assert.Equal (2, verticalResult.Count);
+        Assert.Contains (new Rectangle (0, 0, 4, 10), verticalResult);
+        Assert.Contains (new Rectangle (6, 0, 4, 10), verticalResult);
+
+        // Act:
+        // Now, subtract a horizontal piece from the left fragment.
+        // Horizontal subtract: (0,3,4,2)
+        // from the left fragment (0,0,4,10)
+        // Let's determine the expected fragments:
+        // 1. Top band: since original.Top (0) < subtract.Top (3), we get:
+        //    (0,0,4, 3)  -- height = subtract.Top - original.Top = 3.
+        // 2. Bottom band: since original.Bottom (10) > subtract.Bottom (5), we get:
+        //    (0,5,4, 5)  -- height = original.Bottom - subtract.Bottom = 5.
+        // No left or right fragments are produced because subtractHorizontal spans the full width.
+        // Total fragments: 2.
+        Rectangle subtractHorizontal = new (0, 3, 4, 2);
+        var finalResult = Region.SubtractRectangle (verticalResult [0], subtractHorizontal).ToList ();
+
+        // Assert:
+        // Expecting two fragments: (0,0,4,3) and (0,5,4,5).
+        Assert.Equal (2, finalResult.Count);
+        Assert.Contains (new Rectangle (0, 0, 4, 3), finalResult);
+        Assert.Contains (new Rectangle (0, 5, 4, 5), finalResult);
+    }
+
+}

+ 0 - 2147
UnitTests/Drawing/RegionTests.cs

@@ -1,2147 +0,0 @@
-namespace Terminal.Gui.DrawingTests;
-
-
-
-public class RegionTests
-{
-    [Fact]
-    public void Constructor_EmptyRegion_IsEmpty ()
-    {
-        var region = new Region ();
-        Assert.True (region.IsEmpty ());
-    }
-
-    [Fact]
-    public void Constructor_WithRectangle_IsNotEmpty ()
-    {
-        var region = new Region (new Rectangle (10, 10, 50, 50));
-        Assert.False (region.IsEmpty ());
-    }
-
-    [Fact]
-    public void Union_Rectangle_AddsToRegion ()
-    {
-        var region = new Region ();
-        region.Union (new Rectangle (10, 10, 50, 50));
-        Assert.False (region.IsEmpty ());
-        Assert.True (region.Contains (20, 20));
-    }
-
-    [Fact]
-    public void Union_Region_MergesRegions ()
-    {
-        var region1 = new Region (new Rectangle (10, 10, 50, 50));
-        var region2 = new Region (new Rectangle (30, 30, 50, 50));
-        region1.Union (region2);
-        Assert.True (region1.Contains (20, 20));
-        Assert.True (region1.Contains (40, 40));
-    }
-
-    [Fact]
-    public void Intersect_Rectangle_IntersectsRegion ()
-    {
-        var region = new Region (new Rectangle (10, 10, 50, 50));
-        region.Intersect (new Rectangle (30, 30, 50, 50));
-        Assert.False (region.Contains (20, 20));
-        Assert.True (region.Contains (40, 40));
-    }
-
-    [Fact]
-    public void Intersect_Region_IntersectsRegions ()
-    {
-        var region1 = new Region (new Rectangle (10, 10, 50, 50));
-        var region2 = new Region (new Rectangle (30, 30, 50, 50));
-        region1.Intersect (region2);
-        Assert.False (region1.Contains (20, 20));
-        Assert.True (region1.Contains (40, 40));
-    }
-
-    [Fact]
-    public void Exclude_Rectangle_ExcludesFromRegion ()
-    {
-        var region = new Region (new Rectangle (10, 10, 50, 50));
-        region.Exclude (new Rectangle (20, 20, 20, 20));
-        Assert.False (region.Contains (25, 25));
-        Assert.True (region.Contains (15, 15));
-    }
-
-    [Fact]
-    public void Exclude_Region_ExcludesRegions ()
-    {
-        var region1 = new Region (new Rectangle (10, 10, 50, 50));
-        var region2 = new Region (new Rectangle (20, 20, 20, 20));
-        region1.Exclude (region2);
-        Assert.False (region1.Contains (25, 25));
-        Assert.True (region1.Contains (15, 15));
-    }
-
-    [Fact]
-    public void Complement_Rectangle_ComplementsRegion ()
-    {
-        var region = new Region (new Rectangle (10, 10, 50, 50));
-        region.Complement (new Rectangle (0, 0, 100, 100));
-        Assert.True (region.Contains (5, 5));
-        Assert.False (region.Contains (20, 20));
-    }
-
-    [Fact]
-    public void Clone_CreatesExactCopy ()
-    {
-        var region = new Region (new Rectangle (10, 10, 50, 50));
-        var clone = region.Clone ();
-        Assert.True (clone.Contains (20, 20));
-        Assert.Equal (region.GetRegionScans (), clone.GetRegionScans ());
-    }
-
-    [Fact]
-    public void GetBounds_ReturnsBoundingRectangle ()
-    {
-        var region = new Region (new Rectangle (10, 10, 50, 50));
-        region.Union (new Rectangle (100, 100, 20, 20));
-        var bounds = region.GetBounds ();
-        Assert.Equal (new Rectangle (10, 10, 110, 110), bounds);
-    }
-
-    [Fact]
-    public void IsEmpty_EmptyRegion_ReturnsTrue ()
-    {
-        var region = new Region ();
-        Assert.True (region.IsEmpty ());
-    }
-
-    [Fact]
-    public void Contains_PointInsideRegion_ReturnsTrue ()
-    {
-        var region = new Region (new Rectangle (10, 10, 50, 50));
-        Assert.True (region.Contains (20, 20));
-    }
-
-    [Fact]
-    public void Contains_RectangleInsideRegion_ReturnsTrue ()
-    {
-        var region = new Region (new Rectangle (10, 10, 50, 50));
-        Assert.True (region.Contains (new Rectangle (20, 20, 10, 10)));
-    }
-
-    [Fact]
-    public void GetRegionScans_ReturnsAllRectangles ()
-    {
-        var region = new Region (new Rectangle (10, 10, 50, 50));
-        region.Union (new Rectangle (100, 100, 20, 20));
-        var scans = region.GetRegionScans ();
-        Assert.Equal (2, scans.Length);
-        Assert.Contains (new Rectangle (10, 10, 50, 50), scans);
-        Assert.Contains (new Rectangle (100, 100, 20, 20), scans);
-    }
-    [Fact]
-    public void Union_WithRectangle_AddsRectangle ()
-    {
-        var region = new Region ();
-        var rect = new Rectangle (10, 10, 50, 50);
-
-        region.Union (rect);
-
-        Assert.True (region.Contains (20, 20));
-        Assert.False (region.Contains (100, 100));
-    }
-
-    [Fact]
-    public void Intersect_WithRectangle_IntersectsRectangles ()
-    {
-        var region = new Region (new (10, 10, 50, 50));
-        var rect = new Rectangle (30, 30, 50, 50);
-
-        region.Intersect (rect);
-
-        Assert.True (region.Contains (35, 35));
-        Assert.False (region.Contains (20, 20));
-    }
-
-    [Fact]
-    public void Exclude_WithRectangle_ExcludesRectangle ()
-    {
-        var region = new Region (new (10, 10, 50, 50));
-        var rect = new Rectangle (30, 30, 50, 50);
-
-        region.Exclude (rect);
-
-        Assert.True (region.Contains (20, 20));
-        Assert.False (region.Contains (35, 35));
-    }
-
-    [Fact]
-    public void Contains_Point_ReturnsCorrectResult ()
-    {
-        var region = new Region (new (10, 10, 50, 50));
-
-        Assert.True (region.Contains (20, 20));
-        Assert.False (region.Contains (100, 100));
-    }
-
-    [Fact]
-    public void IsEmpty_ReturnsCorrectResult ()
-    {
-        var region = new Region ();
-
-        Assert.True (region.IsEmpty ());
-
-        region.Union (new Rectangle(10, 10, 50, 50));
-
-        Assert.False (region.IsEmpty ());
-    }
-
-    [Fact]
-    public void GetBounds_ReturnsCorrectBounds ()
-    {
-        var region = new Region ();
-        region.Union (new Rectangle (10, 10, 50, 50));
-        region.Union (new Rectangle (30, 30, 50, 50));
-
-        Rectangle bounds = region.GetBounds ();
-
-        Assert.Equal (new (10, 10, 70, 70), bounds);
-    }
-
-    [Fact]
-    public void Dispose_ClearsRectangles ()
-    {
-        var region = new Region (new (10, 10, 50, 50));
-        region.Dispose ();
-
-        Assert.True (region.IsEmpty ());
-    }
-
-    [Fact]
-    public void Union_WithRegion_AddsRegion ()
-    {
-        var region1 = new Region (new (10, 10, 50, 50));
-        var region2 = new Region (new (30, 30, 50, 50));
-
-        region1.Union (region2.GetBounds ());
-
-        Assert.True (region1.Contains (20, 20));
-        Assert.True (region1.Contains (40, 40));
-    }
-
-    [Fact]
-    public void Intersect_WithRegion_IntersectsRegions ()
-    {
-        var region1 = new Region (new (10, 10, 50, 50));
-        var region2 = new Region (new (30, 30, 50, 50));
-
-        region1.Intersect (region2.GetBounds ());
-
-        Assert.True (region1.Contains (35, 35));
-        Assert.False (region1.Contains (20, 20));
-    }
-
-    [Fact]
-    public void Exclude_WithRegion_ExcludesRegion ()
-    {
-        var region1 = new Region (new (10, 10, 50, 50));
-        var region2 = new Region (new (30, 30, 50, 50));
-
-        region1.Exclude (region2.GetBounds ());
-
-        Assert.True (region1.Contains (20, 20));
-        Assert.False (region1.Contains (35, 35));
-    }
-
-    //[Fact]
-    //public void Complement_WithRectangle_ComplementsRegion ()
-    //{
-    //    var region = new Region (new (10, 10, 50, 50));
-    //    var rect = new Rectangle (30, 30, 50, 50);
-
-    //    region.Complement (rect);
-
-    //    Assert.True (region.Contains (35, 35));
-    //    Assert.False (region.Contains (20, 20));
-    //}
-
-    //[Fact]
-    //public void Complement_WithRegion_ComplementsRegion ()
-    //{
-    //    var region1 = new Region (new (10, 10, 50, 50));
-    //    var region2 = new Region (new (30, 30, 50, 50));
-
-    //    region1.Complement (region2.GetBounds ());
-
-    //    Assert.True (region1.Contains (35, 35));
-    //    Assert.False (region1.Contains (20, 20));
-    //}
-
-    private static Region CreateDisposedRegion ()
-    {
-        Region region = new ();
-        region.Dispose ();
-
-        return region;
-    }
-
-    public static IEnumerable<object []> Region_TestData ()
-    {
-        yield return new object [] { new Region () };
-        yield return new object [] { new Region (new Rectangle (0, 0, 0, 0)) };
-        yield return new object [] { new Region (new Rectangle (1, 2, 3, 4)) };
-    }
-
-    public static IEnumerable<object []> Complement_TestData ()
-    {
-        yield return new object []
-        {
-            new Region (new Rectangle (10, 10, 100, 100)),
-            new Rectangle [] { new (40, 60, 100, 20) },
-            new Rectangle [] { new (110, 60, 30, 20) }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (70, 10, 100, 100)),
-            new Rectangle [] { new (40, 60, 100, 20) },
-            new Rectangle [] { new (40, 60, 30, 20) }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (40, 100, 100, 100)),
-            new Rectangle [] { new (70, 80, 50, 40) },
-            new Rectangle [] { new (70, 80, 50, 20) }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (40, 10, 100, 100)),
-            new Rectangle [] { new (70, 80, 50, 40) },
-            new Rectangle [] { new (70, 110, 50, 10) }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (30, 30, 80, 80)),
-            new Rectangle []
-            {
-                new (45, 45, 200, 200),
-                new (160, 260, 10, 10),
-                new (170, 260, 10, 10),
-            },
-            new Rectangle [] { new (170, 260, 10, 10) }
-        };
-
-        yield return new object []
-        {
-            new Region (),
-            new Rectangle [] { Rectangle.Empty },
-            new Rectangle[0]
-        };
-
-        yield return new object []
-        {
-            new Region (),
-            new Rectangle [] { new (1, 2, 3, 4) },
-            new Rectangle[0]
-        };
-    }
-
-    [Theory]
-    [MemberData (nameof (Complement_TestData))]
-    public void Complement_Region_Success (Region region, Rectangle [] rectangles, Rectangle [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (Rectangle rect in rectangles)
-            {
-                region.Complement (rect);
-            }
-
-            var actualScans = region.GetRegionScans ();
-            Assert.Equal (expectedScans, actualScans);
-        }
-    }
-
-}
-#if x
-
-    [Fact]
-    public void Complement_UnionRegion_Success ()
-    {
-        using Region region = new (new Rectangle (20, 20, 20, 20));
-        using Region other = new (new Rectangle (20, 80, 20, 10));
-        using Matrix matrix = new ();
-        other.Union (new Rectangle (60, 60, 30, 10));
-
-        region.Complement (other);
-
-        Assert.Equal (
-                      [
-                          new (60, 60, 30, 10),
-                          new (20, 80, 20, 10)
-                      ],
-                      region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Complement_InfiniteAndWithIntersectRegion_Success ()
-    {
-        using Region region = new ();
-        using Matrix matrix = new ();
-        region.Intersect (new Rectangle (5, 5, -10, -10));
-        region.Complement (new Rectangle (-5, -5, 12, 12));
-
-        Assert.False (region.IsEmpty (s_graphic));
-        Assert.False (region.IsInfinite (s_graphic));
-
-        Assert.Equal (
-                      [
-                          new (5, -5, 2, 10),
-                          new (-5, 5, 12, 2)
-                      ],
-                      region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Complement_InfiniteRegion_Success ()
-    {
-        using Region region = new (new Rectangle (1, 2, 3, 4));
-        using Matrix matrix = new ();
-        using Region other = new ();
-        region.Complement (other);
-
-        Assert.Equal (
-                      [
-                          new (-4194304, -4194304, 8388608, 4194306),
-                          new (-4194304, 2, 4194305, 4),
-                          new (4, 2, 4194300, 4),
-                          new (-4194304, 6, 8388608, 4194298)
-                      ],
-                      region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Complement_NullRegion_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("region", () => region.Complement ((Region)null));
-    }
-
-
-    [Fact]
-    public void Complement_SameRegion_ThrowsInvalidOperationException ()
-    {
-        using Region region = new ();
-        Assert.Throws<InvalidOperationException> (() => region.Complement (region));
-    }
-
-    [Theory]
-    [MemberData (nameof (Complement_TestData))]
-    public void Complement_Rectangle_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                region.Complement (new Rectangle ((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height));
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Theory]
-    [MemberData (nameof (Complement_TestData))]
-    public void Complement_RectangleF_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                region.Complement (rect);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Theory]
-    [MemberData (nameof (Complement_TestData))]
-    public void Complement_GraphicsPath_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                using GraphicsPath path = new ();
-                path.AddRectangle (rect);
-                region.Complement (path);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Fact]
-    public void Complement_GraphicsPathWithMultipleRectangles_Success ()
-    {
-        Rectangle rect1 = new (20, 30, 60, 80);
-        Rectangle rect2 = new (50, 40, 60, 80);
-
-        using Graphics graphics = Graphics.FromImage (new Bitmap (600, 800));
-        using Region region1 = new (rect1);
-        using Region region2 = new (rect2);
-        using Matrix matrix = new ();
-        graphics.DrawRectangle (Pens.Green, rect1);
-        graphics.DrawRectangle (Pens.Red, rect2);
-
-        region1.Complement (region2);
-        graphics.FillRegion (Brushes.Blue, region1);
-        graphics.DrawRectangles (Pens.Yellow, region1.GetRegionScans (matrix));
-
-        Assert.Equal (
-                      [
-                          new (80, 40, 30, 70),
-                          new (50, 110, 60, 10)
-                      ],
-                      region1.GetRegionScans (matrix));
-    }
-
-
-    public static IEnumerable<object []> Equals_TestData ()
-    {
-        static Region Empty ()
-        {
-            Region emptyRegion = new ();
-            emptyRegion.MakeEmpty ();
-
-            return emptyRegion;
-        }
-
-        Region createdRegion = new ();
-
-        yield return new object [] { createdRegion, createdRegion, true };
-        yield return new object [] { new Region (), new Region (), true };
-        yield return new object [] { new Region (), Empty (), false };
-        yield return new object [] { new Region (), new Region (new Rectangle (1, 2, 3, 4)), false };
-
-        yield return new object [] { Empty (), Empty (), true };
-        yield return new object [] { Empty (), new Region (new Rectangle (0, 0, 0, 0)), true };
-        yield return new object [] { Empty (), new Region (new Rectangle (1, 2, 3, 3)), false };
-
-        yield return new object [] { new Region (new Rectangle (1, 2, 3, 4)), new Region (new Rectangle (1, 2, 3, 4)), true };
-        yield return new object [] { new Region (new Rectangle (1, 2, 3, 4)), new Region (new RectangleF (1, 2, 3, 4)), true };
-        yield return new object [] { new Region (new Rectangle (1, 2, 3, 4)), new Region (new Rectangle (2, 2, 3, 4)), false };
-        yield return new object [] { new Region (new Rectangle (1, 2, 3, 4)), new Region (new Rectangle (1, 3, 3, 4)), false };
-        yield return new object [] { new Region (new Rectangle (1, 2, 3, 4)), new Region (new Rectangle (1, 2, 4, 4)), false };
-        yield return new object [] { new Region (new Rectangle (1, 2, 3, 4)), new Region (new Rectangle (1, 2, 3, 5)), false };
-
-        GraphicsPath graphics1 = new ();
-        graphics1.AddRectangle (new Rectangle (1, 2, 3, 4));
-
-        GraphicsPath graphics2 = new ();
-        graphics2.AddRectangle (new Rectangle (1, 2, 3, 4));
-
-        GraphicsPath graphics3 = new ();
-        graphics3.AddRectangle (new Rectangle (2, 2, 3, 4));
-
-        GraphicsPath graphics4 = new ();
-        graphics4.AddRectangle (new Rectangle (1, 3, 3, 4));
-
-        GraphicsPath graphics5 = new ();
-        graphics5.AddRectangle (new Rectangle (1, 2, 4, 4));
-
-        GraphicsPath graphics6 = new ();
-        graphics6.AddRectangle (new Rectangle (1, 2, 3, 5));
-
-        yield return new object [] { new Region (graphics1), new Region (graphics1), true };
-        yield return new object [] { new Region (graphics1), new Region (graphics2), true };
-        yield return new object [] { new Region (graphics1), new Region (graphics3), false };
-        yield return new object [] { new Region (graphics1), new Region (graphics4), false };
-        yield return new object [] { new Region (graphics1), new Region (graphics5), false };
-        yield return new object [] { new Region (graphics1), new Region (graphics6), false };
-    }
-
-    [Theory]
-    [MemberData (nameof (Equals_TestData))]
-    public void Equals_Valid_ReturnsExpected (Region region, Region other, bool expected)
-    {
-        using (region)
-        using (other)
-        {
-            Assert.Equal (expected, region.Equals (other, s_graphic));
-        }
-    }
-
-    [Fact]
-    public void Equals_NullRegion_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("region", () => region.Equals (null, s_graphic));
-    }
-
-    [Fact]
-    public void Equals_NullGraphics_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("g", () => region.Equals (region, null));
-    }
-
-    [Fact]
-    public void Equals_DisposedGraphics_ThrowsArgumentException ()
-    {
-        using Region region = new ();
-        using Region other = new ();
-        using Bitmap image = new (10, 10);
-        var graphics = Graphics.FromImage (image);
-        graphics.Dispose ();
-        Assert.Throws<ArgumentException> (null, () => region.Equals (region, graphics));
-    }
-
-    [Fact]
-    public void Equals_Disposed_ThrowsArgumentException ()
-    {
-        Region disposedRegion = CreateDisposedRegion ();
-
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Equals (new Region (), s_graphic));
-        Assert.Throws<ArgumentException> (null, () => new Region ().Equals (disposedRegion, s_graphic));
-    }
-
-    public static IEnumerable<object []> Exclude_TestData ()
-    {
-        yield return new object []
-        {
-            new Region (new Rectangle (500, 30, 60, 80)),
-            new RectangleF [] { new (500, 30, 60, 80) },
-            new RectangleF[0]
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (500, 30, 60, 80)),
-            new RectangleF [] { RectangleF.Empty },
-            new RectangleF [] { new (500, 30, 60, 80) }
-        };
-
-        yield return new object []
-        {
-            new Region (),
-            new RectangleF [] { new (520, 40, 60, 80) },
-            new RectangleF []
-            {
-                new (-4194304, -4194304, 8388608, 4194344),
-                new (-4194304, 40, 4194824, 80),
-                new (580, 40, 4193724, 80),
-                new (-4194304, 120, 8388608, 4194184)
-            }
-        };
-
-        yield return new object []
-        {
-            new Region (),
-            new RectangleF [] { RectangleF.Empty },
-            new RectangleF [] { new Rectangle (-4194304, -4194304, 8388608, 8388608) }
-        };
-
-        // Intersecting from the right.
-        yield return new object []
-        {
-            new Region (new Rectangle (10, 10, 100, 100)),
-            new RectangleF [] { new (40, 60, 100, 20) },
-            new RectangleF []
-            {
-                new (10, 10, 100, 50),
-                new (10, 60, 30, 20),
-                new (10, 80, 100, 30)
-            }
-        };
-
-        // Intersecting from the left.
-        yield return new object []
-        {
-            new Region (new Rectangle (70, 10, 100, 100)),
-            new RectangleF [] { new (40, 60, 100, 20) },
-            new RectangleF []
-            {
-                new (70, 10, 100, 50),
-                new (140, 60, 30, 20),
-                new (70, 80, 100, 30)
-            }
-        };
-
-        // Intersecting from the top.
-        yield return new object []
-        {
-            new Region (new Rectangle (40, 100, 100, 100)),
-            new RectangleF [] { new (70, 80, 50, 40) },
-            new RectangleF []
-            {
-                new (40, 100, 30, 20),
-                new (120, 100, 20, 20),
-                new (40, 120, 100, 80)
-            }
-        };
-
-        // Intersecting from the bottom.
-        yield return new object []
-        {
-            new Region (new Rectangle (40, 10, 100, 100)),
-            new RectangleF [] { new (70, 80, 50, 40) },
-            new RectangleF []
-            {
-                new (40, 10, 100, 70),
-                new (40, 80, 30, 30),
-                new (120, 80, 20, 30)
-            }
-        };
-
-        // Multiple regions.
-        yield return new object []
-        {
-            new Region (new Rectangle (30, 30, 80, 80)),
-            new RectangleF []
-            {
-                new (45, 45, 200, 200),
-                new (160, 260, 10, 10),
-                new (170, 260, 10, 10)
-            },
-            new RectangleF []
-            {
-                new (30, 30, 80, 15),
-                new (30, 45, 15, 65)
-            }
-        };
-
-        // Intersecting from the top with a larger rect.
-        yield return new object []
-        {
-            new Region (new Rectangle (50, 100, 100, 100)),
-            new RectangleF [] { new (30, 70, 150, 40) },
-            new RectangleF [] { new (50, 110, 100, 90) }
-        };
-
-        // Intersecting from the right with a larger rect.
-        yield return new object []
-        {
-            new Region (new Rectangle (70, 60, 100, 70)),
-            new RectangleF [] { new (40, 10, 100, 150) },
-            new RectangleF [] { new (140, 60, 30, 70) }
-        };
-
-        // Intersecting from the left with a larger rect.
-        yield return new object []
-        {
-            new Region (new Rectangle (70, 60, 100, 70)),
-            new RectangleF [] { new (100, 10, 100, 150) },
-            new RectangleF [] { new (70, 60, 30, 70) }
-        };
-
-        // Intersecting from the bottom with a larger rect.
-        yield return new object []
-        {
-            new Region (new Rectangle (20, 20, 100, 100)),
-            new RectangleF [] { new (10, 80, 140, 150) },
-            new RectangleF [] { new (20, 20, 100, 60) }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (130, 30, 60, 80)),
-            new RectangleF [] { new (170, 40, 60, 80) },
-            new RectangleF []
-            {
-                new (130, 30, 60, 10),
-                new (130, 40, 40, 70)
-            }
-        };
-    }
-
-    [Theory]
-    [MemberData (nameof (Exclude_TestData))]
-    public void Exclude_Region_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                using Region other = new (rect);
-                region.Exclude (other);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Fact]
-    public void Exclude_UnionRegion_Success ()
-    {
-        using Region region = new (new RectangleF (20, 20, 20, 20));
-        using Region union = new (new RectangleF (20, 80, 20, 10));
-        using Matrix matrix = new ();
-        union.Union (new RectangleF (60, 60, 30, 10));
-        region.Exclude (union);
-        Assert.Equal ([new (20, 20, 20, 20)], region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Exclude_InfiniteRegion_Success ()
-    {
-        using Region region = new (new Rectangle (1, 2, 3, 4));
-        using Region other = new ();
-        using Matrix matrix = new ();
-        region.Exclude (other);
-        Assert.Equal ([], region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Exclude_NullRegion_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("region", () => region.Exclude ((Region)null));
-    }
-
-    [Fact]
-    public void Exclude_DisposedRegion_ThrowsArgumentException ()
-    {
-        Assert.Throws<ArgumentException> (null, () => new Region ().Exclude (CreateDisposedRegion ()));
-    }
-
-    [Fact]
-    public void Exclude_SameRegion_ThrowsInvalidOperationException ()
-    {
-        using Region region = new ();
-        Assert.Throws<InvalidOperationException> (() => region.Exclude (region));
-    }
-
-    [Theory]
-    [MemberData (nameof (Exclude_TestData))]
-    public void Exclude_Rectangle_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                region.Exclude (new Rectangle ((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height));
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Theory]
-    [MemberData (nameof (Exclude_TestData))]
-    public void Exclude_RectangleF_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                region.Exclude (rect);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Theory]
-    [MemberData (nameof (Exclude_TestData))]
-    public void Exclude_GraphicsPath_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                using GraphicsPath path = new ();
-                path.AddRectangle (rect);
-                region.Exclude (path);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Fact]
-    public void Exclude_EmptyPathWithInfiniteRegion_MakesInfinite ()
-    {
-        using Region region = new ();
-        using GraphicsPath graphicsPath = new ();
-        region.Exclude (graphicsPath);
-        Assert.True (region.IsInfinite (s_graphic));
-    }
-
-    [Fact]
-    public void Exclude_NullGraphicsPath_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("path", () => region.Exclude ((GraphicsPath)null));
-    }
-
-    [Fact]
-    public void Exclude_Disposed_ThrowsArgumentException ()
-    {
-        Region disposedRegion = CreateDisposedRegion ();
-
-        using GraphicsPath graphicsPath = new ();
-        using Region other = new ();
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Exclude (graphicsPath));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Exclude (new Rectangle ()));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Exclude (new RectangleF ()));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Exclude (other));
-    }
-
-    [Fact]
-    public void FromHrgn_ValidHrgn_ReturnsExpected ()
-    {
-        using Region region = new (new Rectangle (1, 2, 3, 4));
-        IntPtr handle1 = region.GetHrgn (s_graphic);
-        IntPtr handle2 = region.GetHrgn (s_graphic);
-        Assert.NotEqual (IntPtr.Zero, handle1);
-        Assert.NotEqual (handle1, handle2);
-
-        Region newRegion = Region.FromHrgn (handle1);
-        IntPtr handle3 = newRegion.GetHrgn (s_graphic);
-        Assert.NotEqual (handle3, handle1);
-        Assert.Equal (new RectangleF (1, 2, 3, 4), newRegion.GetBounds (s_graphic));
-
-        region.ReleaseHrgn (handle1);
-        region.ReleaseHrgn (handle2);
-        newRegion.ReleaseHrgn (handle3);
-    }
-
-    [Fact]
-    public void FromHrgn_ZeroHrgn_ThrowsArgumentException () { Assert.Throws<ArgumentException> (null, () => Region.FromHrgn (IntPtr.Zero)); }
-
-    [Fact]
-    public void GetHrgn_Infinite_ReturnsZero ()
-    {
-        using Region region = new (new Rectangle (1, 2, 3, 4));
-        IntPtr handle = region.GetHrgn (s_graphic);
-        Assert.NotEqual (IntPtr.Zero, handle);
-        region.ReleaseHrgn (handle);
-
-        region.MakeInfinite ();
-        Assert.Equal (IntPtr.Zero, region.GetHrgn (s_graphic));
-    }
-
-    [Fact]
-    public void GetHrgn_Empty_ReturnsNonZero ()
-    {
-        using Region region = new ();
-        Assert.Equal (IntPtr.Zero, region.GetHrgn (s_graphic));
-
-        region.MakeEmpty ();
-        IntPtr handle = region.GetHrgn (s_graphic);
-        Assert.NotEqual (IntPtr.Zero, handle);
-        region.ReleaseHrgn (handle);
-    }
-
-    [Fact]
-    public void GetHrgn_NullGraphics_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("g", () => region.GetHrgn (null));
-    }
-
-    [Fact]
-    public void GetHrgn_Disposed_ThrowsArgumentException ()
-    {
-        Assert.Throws<ArgumentException> (null, () => CreateDisposedRegion ().GetHrgn (s_graphic));
-    }
-
-    [Fact]
-    public void ReleaseHrgn_ZeroHandle_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("regionHandle", () => region.ReleaseHrgn (IntPtr.Zero));
-    }
-
-    [Fact]
-    public void GetBounds_NullGraphics_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("g", () => region.GetBounds (null));
-    }
-
-    [Fact]
-    public void GetBounds_DisposedGraphics_ThrowsArgumentException ()
-    {
-        using Region region = new ();
-        using Bitmap image = new (10, 10);
-        var graphics = Graphics.FromImage (image);
-        graphics.Dispose ();
-        Assert.Throws<ArgumentException> (null, () => region.GetBounds (graphics));
-    }
-
-    [Fact]
-    public void GetBounds_Disposed_ThrowsArgumentException ()
-    {
-        Assert.Throws<ArgumentException> (null, () => CreateDisposedRegion ().GetBounds (s_graphic));
-    }
-
-    [Fact]
-    public void GetRegionData_Disposed_ThrowsArgumentException ()
-    {
-        Assert.Throws<ArgumentException> (null, () => CreateDisposedRegion ().GetRegionData ());
-    }
-
-    [Fact]
-    public void GetRegionScans_CustomMatrix_TransformsRegionScans ()
-    {
-        using Matrix matrix = new ();
-        using Region region = new (new Rectangle (1, 2, 3, 4));
-        using Matrix emptyMatrix = new ();
-        matrix.Translate (10, 11);
-        matrix.Scale (5, 6);
-
-        Assert.Equal ([new (1, 2, 3, 4)], region.GetRegionScans (emptyMatrix));
-        Assert.Equal ([new (15, 23, 15, 24)], region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void GetRegionScans_NullMatrix_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("matrix", () => region.GetRegionScans (null));
-    }
-
-    [Fact]
-    public void GetRegionScans_Disposed_ThrowsArgumentException ()
-    {
-        using Matrix matrix = new ();
-        Assert.Throws<ArgumentException> (null, () => CreateDisposedRegion ().GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void GetRegionScans_DisposedMatrix_ThrowsArgumentException ()
-    {
-        using Region region = new ();
-        Matrix matrix = new ();
-        matrix.Dispose ();
-        Assert.Throws<ArgumentException> (null, () => region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Intersect_SmallerRect_Success ()
-    {
-        using Region clipRegion = new ();
-        using Matrix matrix = new ();
-        Rectangle smaller = new (5, 5, -10, -10);
-
-        clipRegion.Intersect (smaller);
-        Assert.False (clipRegion.IsEmpty (s_graphic));
-        Assert.False (clipRegion.IsInfinite (s_graphic));
-
-        RectangleF [] rects = clipRegion.GetRegionScans (matrix);
-        Assert.Equal (1, rects.Length);
-        Assert.Equal (new RectangleF (-5, -5, 10, 10), rects [0]);
-    }
-
-    public static IEnumerable<object []> Intersect_TestData ()
-    {
-        yield return new object []
-        {
-            new Region (new Rectangle (500, 30, 60, 80)),
-            new RectangleF [] { new (500, 30, 60, 80) },
-            new RectangleF [] { new (500, 30, 60, 80) }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (0, 0, 0, 0)),
-            new RectangleF [] { new (500, 30, 60, 80) },
-            new RectangleF[0]
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (500, 30, 60, 80)),
-            new RectangleF [] { RectangleF.Empty },
-            new RectangleF[0]
-        };
-
-        yield return new object []
-        {
-            new Region (),
-            new RectangleF [] { new (520, 40, 60, 80) },
-            new RectangleF [] { new Rectangle (520, 40, 60, 80) }
-        };
-
-        yield return new object []
-        {
-            new Region (),
-            new RectangleF [] { RectangleF.Empty },
-            new RectangleF[0]
-        };
-
-        yield return new object []
-        {
-            new Region (new RectangleF (260, 30, 60, 80)),
-            new RectangleF [] { new (290, 40, 60, 90) },
-            new RectangleF [] { new (290, 40, 30, 70) }
-        };
-
-        yield return new object []
-        {
-            new Region (new RectangleF (20, 330, 40, 50)),
-            new RectangleF []
-            {
-                new (50, 340, 40, 50),
-                new (70, 360, 30, 50),
-                new (80, 400, 30, 10)
-            },
-            new RectangleF[0]
-        };
-    }
-
-    [Theory]
-    [MemberData (nameof (Intersect_TestData))]
-    public void Intersect_Region_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                using Region rectangleRegion = new (rect);
-                region.Intersect (rectangleRegion);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Fact]
-    public void Intersect_InfiniteRegion_Success ()
-    {
-        using Region region = new (new Rectangle (1, 2, 3, 4));
-        using Matrix matrix = new ();
-        using Region infiniteRegion = new ();
-        region.Intersect (infiniteRegion);
-
-        Assert.Equal ([new Rectangle (1, 2, 3, 4)], region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Intersect_NullRegion_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("region", () => region.Intersect ((Region)null));
-    }
-
-    [Fact]
-    public void Intersect_DisposedRegion_ThrowsArgumentException ()
-    {
-        Assert.Throws<ArgumentException> (null, () => new Region ().Intersect (CreateDisposedRegion ()));
-    }
-
-    [Fact]
-    public void Intersect_SameRegion_ThrowsInvalidOperationException ()
-    {
-        using Region region = new ();
-        Assert.Throws<InvalidOperationException> (() => region.Intersect (region));
-    }
-
-    [Theory]
-    [MemberData (nameof (Intersect_TestData))]
-    public void Intersect_Rectangle_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                region.Intersect (new Rectangle ((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height));
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Fact]
-    public void Intersect_InfiniteRegionWithSmallerRectangle_Success ()
-    {
-        using Region region = new ();
-        using Matrix matrix = new ();
-        region.Intersect (new Rectangle (5, 5, -10, -10));
-
-        Assert.False (region.IsEmpty (s_graphic));
-        Assert.False (region.IsInfinite (s_graphic));
-        Assert.Equal ([new (-5, -5, 10, 10)], region.GetRegionScans (matrix));
-    }
-
-    [Theory]
-    [MemberData (nameof (Intersect_TestData))]
-    public void Intersect_RectangleF_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                region.Intersect (rect);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Fact]
-    public void Intersect_InfiniteRegionWithSmallerRectangleF_Success ()
-    {
-        using Region region = new ();
-        using Matrix matrix = new ();
-        region.Intersect (new RectangleF (5, 5, -10, -10));
-
-        Assert.False (region.IsEmpty (s_graphic));
-        Assert.False (region.IsInfinite (s_graphic));
-        Assert.Equal ([new (-5, -5, 10, 10)], region.GetRegionScans (matrix));
-    }
-
-    [Theory]
-    [MemberData (nameof (Intersect_TestData))]
-    public void Intersect_GraphicsPath_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                using GraphicsPath path = new ();
-                path.AddRectangle (rect);
-                region.Intersect (path);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Fact]
-    public void Intersect_EmptyPathWithInfiniteRegion_MakesEmpty ()
-    {
-        using Region region = new ();
-        using GraphicsPath graphicsPath = new ();
-        region.Intersect (graphicsPath);
-        Assert.True (region.IsEmpty (s_graphic));
-    }
-
-    [Fact]
-    public void Intersect_NullGraphicsPath_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("path", () => region.Intersect ((GraphicsPath)null));
-    }
-
-    [Fact]
-    public void Intersect_Disposed_ThrowsArgumentException ()
-    {
-        Region disposedRegion = CreateDisposedRegion ();
-
-        using GraphicsPath graphicsPath = new ();
-        using Region other = new ();
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Intersect (graphicsPath));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Intersect (new Rectangle ()));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Intersect (new RectangleF ()));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Intersect (other));
-    }
-
-    [Fact]
-    public void IsEmpty_NullGraphics_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("g", () => region.IsEmpty (null));
-    }
-
-    [Fact]
-    public void IsEmpty_Disposed_ThrowsArgumentException ()
-    {
-        Assert.Throws<ArgumentException> (null, () => CreateDisposedRegion ().IsEmpty (s_graphic));
-    }
-
-    [Fact]
-    public void IsInfinite_NullGraphics_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("g", () => region.IsInfinite (null));
-    }
-
-    [Fact]
-    public void IsInfinite_DisposedGraphics_ThrowsArgumentException ()
-    {
-        using Region region = new ();
-        using Bitmap image = new (10, 10);
-        var graphics = Graphics.FromImage (image);
-        graphics.Dispose ();
-        Assert.Throws<ArgumentException> (null, () => region.IsInfinite (graphics));
-    }
-
-    [Fact]
-    public void IsInfinite_Disposed_ThrowsArgumentException ()
-    {
-        Assert.Throws<ArgumentException> (null, () => CreateDisposedRegion ().IsInfinite (s_graphic));
-    }
-
-    public static IEnumerable<object []> IsVisible_Rectangle_TestData ()
-    {
-        Region infiniteExclude = new ();
-        infiniteExclude.Exclude (new Rectangle (387, 292, 189, 133));
-        infiniteExclude.Exclude (new Rectangle (387, 66, 189, 133));
-
-        yield return new object [] { infiniteExclude, new Rectangle (66, 292, 189, 133), true };
-        yield return new object [] { new Region (), Rectangle.Empty, false };
-
-        yield return new object [] { new Region (new Rectangle (0, 0, 10, 10)), new Rectangle (0, 0, 0, 1), false };
-        yield return new object [] { new Region (new Rectangle (500, 30, 60, 80)), new Rectangle (500, 30, 60, 80), true };
-        yield return new object [] { new Region (new Rectangle (500, 30, 60, 80)), new Rectangle (520, 40, 60, 80), true };
-
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Rectangle (1, 1, 2, 1), true };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Rectangle (1, 1, 2, 2), true };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Rectangle (1, 1, 10, 10), true };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Rectangle (1, 1, 1, 1), true };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Rectangle (2, 2, 1, 1), false };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Rectangle (0, 0, 1, 1), false };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Rectangle (3, 3, 1, 1), false };
-    }
-
-    [Theory]
-    [MemberData (nameof (IsVisible_Rectangle_TestData))]
-    public void IsVisible_Rectangle_ReturnsExpected (Region region, Rectangle rectangle, bool expected)
-    {
-        using (region)
-        using (Bitmap image = new (10, 10))
-        {
-            var disposedGraphics = Graphics.FromImage (image);
-            disposedGraphics.Dispose ();
-
-            Assert.Equal (expected, region.IsVisible (rectangle));
-            Assert.Equal (expected, region.IsVisible ((RectangleF)rectangle));
-            Assert.Equal (expected, region.IsVisible (rectangle, s_graphic));
-            Assert.Equal (expected, region.IsVisible (rectangle, disposedGraphics));
-            Assert.Equal (expected, region.IsVisible (rectangle, null));
-            Assert.Equal (expected, region.IsVisible ((RectangleF)rectangle, s_graphic));
-            Assert.Equal (expected, region.IsVisible ((RectangleF)rectangle, disposedGraphics));
-            Assert.Equal (expected, region.IsVisible ((RectangleF)rectangle, null));
-
-            Assert.Equal (expected, region.IsVisible (rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height));
-            Assert.Equal (expected, region.IsVisible ((float)rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height));
-            Assert.Equal (expected, region.IsVisible (rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, s_graphic));
-            Assert.Equal (expected, region.IsVisible (rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, disposedGraphics));
-            Assert.Equal (expected, region.IsVisible (rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, null));
-            Assert.Equal (expected, region.IsVisible ((float)rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, s_graphic));
-            Assert.Equal (expected, region.IsVisible ((float)rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, disposedGraphics));
-            Assert.Equal (expected, region.IsVisible ((float)rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, null));
-        }
-    }
-
-    public static IEnumerable<object []> IsVisible_Point_TestData ()
-    {
-        Region infiniteExclude = new ();
-        infiniteExclude.Exclude (new Rectangle (387, 292, 189, 133));
-        infiniteExclude.Exclude (new Rectangle (387, 66, 189, 133));
-
-        yield return new object [] { infiniteExclude, new Point (66, 292), true };
-        yield return new object [] { new Region (), Point.Empty, true };
-
-        yield return new object [] { new Region (new Rectangle (500, 30, 60, 80)), new Point (500, 29), false };
-        yield return new object [] { new Region (new Rectangle (500, 30, 60, 80)), new Point (500, 30), true };
-
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Point (0, 1), false };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Point (1, 0), false };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Point (2, 0), false };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Point (3, 0), false };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Point (1, 1), true };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Point (2, 1), true };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Point (3, 1), false };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Point (0, 2), false };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Point (2, 2), false };
-        yield return new object [] { new Region (new Rectangle (1, 1, 2, 1)), new Point (3, 2), false };
-    }
-
-    [Theory]
-    [MemberData (nameof (IsVisible_Point_TestData))]
-    public void IsVisible_Point_ReturnsExpected (Region region, Point point, bool expected)
-    {
-        using (region)
-        using (Bitmap image = new (10, 10))
-        {
-            var disposedGraphics = Graphics.FromImage (image);
-            disposedGraphics.Dispose ();
-
-            Assert.Equal (expected, region.IsVisible (point));
-            Assert.Equal (expected, region.IsVisible ((PointF)point));
-            Assert.Equal (expected, region.IsVisible (point, s_graphic));
-            Assert.Equal (expected, region.IsVisible (point, disposedGraphics));
-            Assert.Equal (expected, region.IsVisible (point, null));
-            Assert.Equal (expected, region.IsVisible ((PointF)point, s_graphic));
-            Assert.Equal (expected, region.IsVisible ((PointF)point, disposedGraphics));
-            Assert.Equal (expected, region.IsVisible ((PointF)point, null));
-
-            Assert.Equal (expected, region.IsVisible (point.X, point.Y));
-            Assert.Equal (expected, region.IsVisible (point.X, point.Y, s_graphic));
-            Assert.Equal (expected, region.IsVisible (point.X, point.Y, disposedGraphics));
-            Assert.Equal (expected, region.IsVisible (point.X, point.Y, null));
-
-            Assert.Equal (expected, region.IsVisible (point.X, point.Y, s_graphic));
-            Assert.Equal (expected, region.IsVisible (point.X, point.Y, disposedGraphics));
-            Assert.Equal (expected, region.IsVisible (point.X, point.Y, null));
-            Assert.Equal (expected, region.IsVisible ((float)point.X, point.Y, s_graphic));
-            Assert.Equal (expected, region.IsVisible ((float)point.X, point.Y, disposedGraphics));
-            Assert.Equal (expected, region.IsVisible ((float)point.X, point.Y, null));
-        }
-    }
-
-    [Fact]
-    public void IsVisible_Disposed_ThrowsArgumentException ()
-    {
-        Region disposedRegion = CreateDisposedRegion ();
-
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (1f, 2f));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (new PointF (1, 2)));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (new Point (1, 2)));
-
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (1f, 2f, s_graphic));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (new PointF (1, 2), s_graphic));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (new Point (1, 2), s_graphic));
-
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (1f, 2f, 3f, 4f));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (new Rectangle (1, 2, 3, 4)));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (new RectangleF (1, 2, 3, 4)));
-
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (1f, 2f, 3f, 4f, s_graphic));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (new Rectangle (1, 2, 3, 4), s_graphic));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (new RectangleF (1, 2, 3, 4), s_graphic));
-
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (1, 2, s_graphic));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (1, 2, 3, 4));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.IsVisible (1, 2, 3, 4, s_graphic));
-    }
-
-    [Theory]
-    [MemberData (nameof (Region_TestData))]
-    public void MakeEmpty_NonEmpty_Success (Region region)
-    {
-        using (region)
-        {
-            region.MakeEmpty ();
-            Assert.True (region.IsEmpty (s_graphic));
-            Assert.False (region.IsInfinite (s_graphic));
-            Assert.Equal (RectangleF.Empty, region.GetBounds (s_graphic));
-
-            using (Matrix matrix = new ())
-            {
-                Assert.Empty (region.GetRegionScans (matrix));
-            }
-
-            region.MakeEmpty ();
-            Assert.True (region.IsEmpty (s_graphic));
-        }
-    }
-
-    [Fact]
-    public void MakeEmpty_Disposed_ThrowsArgumentException () { Assert.Throws<ArgumentException> (null, () => CreateDisposedRegion ().MakeEmpty ()); }
-
-    [Theory]
-    [MemberData (nameof (Region_TestData))]
-    public void MakeInfinite_NonInfinity_Success (Region region)
-    {
-        using (region)
-        {
-            region.MakeInfinite ();
-            Assert.False (region.IsEmpty (s_graphic));
-            Assert.True (region.IsInfinite (s_graphic));
-            Assert.Equal (new RectangleF (-4194304, -4194304, 8388608, 8388608), region.GetBounds (s_graphic));
-
-            region.MakeInfinite ();
-            Assert.False (region.IsEmpty (s_graphic));
-            Assert.True (region.IsInfinite (s_graphic));
-        }
-    }
-
-    [Fact]
-    public void MakeInfinite_Disposed_ThrowsArgumentException ()
-    {
-        Assert.Throws<ArgumentException> (null, () => CreateDisposedRegion ().MakeInfinite ());
-    }
-
-    public static IEnumerable<object []> Union_TestData ()
-    {
-        yield return new object []
-        {
-            new Region (new Rectangle (500, 30, 60, 80)),
-            new RectangleF [] { new (500, 30, 60, 80) },
-            new RectangleF [] { new (500, 30, 60, 80) }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (500, 30, 60, 80)),
-            new RectangleF [] { RectangleF.Empty },
-            new RectangleF [] { new (500, 30, 60, 80) }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (500, 30, 60, 80)),
-            new RectangleF [] { new (520, 30, 60, 80) },
-            new RectangleF [] { new (500, 30, 80, 80) }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (500, 30, 60, 80)),
-            new RectangleF [] { new (520, 40, 60, 80) },
-            new RectangleF []
-            {
-                new (500, 30, 60, 10),
-                new (500, 40, 80, 70),
-                new (520, 110, 60, 10),
-            }
-        };
-
-        yield return new object []
-        {
-            new Region (),
-            new RectangleF [] { new (520, 40, 60, 80) },
-            new RectangleF [] { new Rectangle (-4194304, -4194304, 8388608, 8388608) }
-        };
-
-        yield return new object []
-        {
-            new Region (),
-            new RectangleF [] { RectangleF.Empty },
-            new RectangleF [] { new Rectangle (-4194304, -4194304, 8388608, 8388608) }
-        };
-
-        // No intersecting rects.
-        yield return new object []
-        {
-            new Region (new Rectangle (20, 20, 20, 20)),
-            new RectangleF []
-            {
-                new (20, 80, 20, 10),
-                new (60, 60, 30, 10)
-            },
-            new RectangleF []
-            {
-                new (20, 20, 20, 20),
-                new (60, 60, 30, 10),
-                new (20, 80, 20, 10)
-            }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (20, 180, 40, 50)),
-            new RectangleF []
-            {
-                new (50, 190, 40, 50),
-                new (70, 210, 30, 50)
-            },
-            new RectangleF []
-            {
-                new (20, 180, 40, 10),
-                new (20, 190, 70, 20),
-                new (20, 210, 80, 20),
-                new (50, 230, 50, 10),
-                new (70, 240, 30, 20)
-            }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (20, 330, 40, 50)),
-            new RectangleF []
-            {
-                new (50, 340, 40, 50),
-                new (70, 360, 30, 50),
-                new (80, 400, 30, 10)
-            },
-            new RectangleF []
-            {
-                new (20, 330, 40, 10),
-                new (20, 340, 70, 20),
-                new (20, 360, 80, 20),
-                new (50, 380, 50, 10),
-                new (70, 390, 30, 10),
-                new (70, 400, 40, 10)
-            }
-        };
-
-        yield return new object []
-        {
-            new Region (new Rectangle (10, 20, 50, 50)),
-            new RectangleF []
-            {
-                new (100, 100, 60, 60),
-                new (200, 200, 80, 80)
-            },
-            new RectangleF []
-            {
-                new (10, 20, 50, 50),
-                new (100, 100, 60, 60),
-                new (200, 200, 80, 80)
-            }
-        };
-
-        // Intersecting from the right.
-        yield return new object []
-        {
-            new Region (new Rectangle (10, 10, 100, 100)),
-            new RectangleF [] { new (40, 60, 100, 20) },
-            new RectangleF []
-            {
-                new (10, 10, 100, 50),
-                new (10, 60, 130, 20),
-                new (10, 80, 100, 30)
-            }
-        };
-
-        // Intersecting from the left.
-        yield return new object []
-        {
-            new Region (new Rectangle (70, 10, 100, 100)),
-            new RectangleF [] { new (40, 60, 100, 20) },
-            new RectangleF []
-            {
-                new (70, 10, 100, 50),
-                new (40, 60, 130, 20),
-                new (70, 80, 100, 30)
-            }
-        };
-
-        // Intersecting from the top.
-        yield return new object []
-        {
-            new Region (new Rectangle (40, 100, 100, 100)),
-            new RectangleF [] { new (70, 80, 50, 40) },
-            new RectangleF []
-            {
-                new (70, 80, 50, 20),
-                new (40, 100, 100, 100)
-            }
-        };
-
-        // Intersecting from the bottom.
-        yield return new object []
-        {
-            new Region (new Rectangle (40, 10, 100, 100)),
-            new RectangleF [] { new (70, 80, 50, 40) },
-            new RectangleF []
-            {
-                new (40, 10, 100, 100),
-                new (70, 110, 50, 10)
-            }
-        };
-
-        // Multiple regions separated by 0 pixels.
-        yield return new object []
-        {
-            new Region (new Rectangle (30, 30, 80, 80)),
-            new RectangleF []
-            {
-                new (45, 45, 200, 200),
-                new (160, 260, 10, 10),
-                new (170, 260, 10, 10)
-            },
-            new RectangleF []
-            {
-                new (30, 30, 80, 15),
-                new (30, 45, 215, 65),
-                new (45, 110, 200, 135),
-                new (160, 260, 20, 10)
-            }
-        };
-    }
-
-    [Theory]
-    [MemberData (nameof (Union_TestData))]
-    public void Union_Region_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                using Region other = new (rect);
-                region.Union (other);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Fact]
-    public void Union_InfiniteRegion_Success ()
-    {
-        using Region region = new (new Rectangle (1, 2, 3, 4));
-        using Region other = new ();
-        using Matrix matrix = new ();
-        region.Union (other);
-
-        Assert.Equal ([new Rectangle (-4194304, -4194304, 8388608, 8388608)], region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Union_NullRegion_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("region", () => region.Union ((Region)null));
-    }
-
-    [Fact]
-    public void Union_DisposedRegion_ThrowsArgumentException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentException> (null, () => region.Union (CreateDisposedRegion ()));
-    }
-
-    [Fact]
-    public void Union_SameRegion_ThrowsInvalidOperationException ()
-    {
-        using Region region = new ();
-        Assert.Throws<InvalidOperationException> (() => region.Union (region));
-    }
-
-    [Theory]
-    [MemberData (nameof (Union_TestData))]
-    public void Union_Rectangle_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                region.Union (new Rectangle ((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height));
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Theory]
-    [MemberData (nameof (Union_TestData))]
-    public void Union_RectangleF_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                region.Union (rect);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Theory]
-    [MemberData (nameof (Union_TestData))]
-    public void Union_GraphicsPath_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                using GraphicsPath path = new ();
-                path.AddRectangle (rect);
-                region.Union (path);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Fact]
-    public void Union_EmptyPathWithInfiniteRegion_MakesInfinite ()
-    {
-        using Region region = new ();
-        using GraphicsPath graphicsPath = new ();
-        region.Union (graphicsPath);
-        Assert.True (region.IsInfinite (s_graphic));
-    }
-
-    [Fact]
-    public void Union_NullGraphicsPath_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("path", () => region.Union ((GraphicsPath)null));
-    }
-
-    [Fact]
-    public void Union_Disposed_ThrowsArgumentException ()
-    {
-        Region disposedRegion = CreateDisposedRegion ();
-
-        using GraphicsPath graphicsPath = new ();
-        using Region other = new ();
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Union (graphicsPath));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Union (new Rectangle ()));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Union (new RectangleF ()));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Union (disposedRegion));
-    }
-
-    [Fact]
-    public void Transform_EmptyMatrix_Nop ()
-    {
-        using Region region = new (new RectangleF (1, 2, 3, 4));
-        using Matrix matrix = new ();
-        region.Transform (matrix);
-        Assert.Equal ([new (1, 2, 3, 4)], region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Transform_CustomMatrix_Success ()
-    {
-        using Region region = new (new RectangleF (1, 2, 3, 4));
-        using Matrix matrix = new ();
-        using Matrix emptyMatrix = new ();
-        matrix.Translate (10, 11);
-        matrix.Scale (5, 6);
-
-        region.Transform (matrix);
-        Assert.Equal ([new (15, 23, 15, 24)], region.GetRegionScans (emptyMatrix));
-    }
-
-    [Theory]
-    [InlineData (0, 0, 0)]
-    [InlineData (2, 2, 0)]
-    [InlineData (0.5, 0.5, 0)]
-    [InlineData (1, 1, 45)]
-    public void Transform_Infinity_Nop (float scaleX, float scaleY, int angle)
-    {
-        using Region region = new ();
-        using Matrix matrix = new ();
-        using Matrix emptyMatrix = new ();
-        matrix.Translate (10, 11);
-        matrix.Scale (scaleX, scaleY);
-        matrix.Rotate (angle);
-
-        region.Transform (matrix);
-        Assert.True (region.IsInfinite (s_graphic));
-        Assert.Equal ([new (-4194304, -4194304, 8388608, 8388608)], region.GetRegionScans (emptyMatrix));
-    }
-
-    [Fact]
-    public void Transform_InfinityIntersectScale_Success ()
-    {
-        using Region region = new ();
-        using Matrix matrix = new ();
-        using Matrix emptyMatrix = new ();
-        matrix.Scale (2, 0.5f);
-
-        region.Intersect (new Rectangle (-10, -10, 20, 20));
-        region.Transform (matrix);
-        Assert.False (region.IsInfinite (s_graphic));
-        Assert.Equal ([new (-20, -5, 40, 10)], region.GetRegionScans (emptyMatrix));
-    }
-
-    [Fact]
-    public void Transform_InfinityIntersectTransform_Success ()
-    {
-        using Region region = new ();
-        using Matrix matrix = new (2, 0, 0, 0.5f, 10, 10);
-        using Matrix emptyMatrix = new ();
-        region.Intersect (new Rectangle (-10, -10, 20, 20));
-        region.Transform (matrix);
-
-        Assert.False (region.IsInfinite (s_graphic));
-        Assert.Equal ([new (-10, 5, 40, 10)], region.GetRegionScans (emptyMatrix));
-    }
-
-    [Fact]
-    public void Transform_NullMatrix_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("matrix", () => region.Transform (null));
-    }
-
-    [Fact]
-    public void Transform_Disposed_ThrowsArgumentException ()
-    {
-        using Matrix matrix = new ();
-        Assert.Throws<ArgumentException> (null, () => CreateDisposedRegion ().Transform (matrix));
-    }
-
-    [Theory]
-    [InlineData (0, 0)]
-    [InlineData (2, 3)]
-    [InlineData (-2, -3)]
-    public void Translate_Int_Success (float dx, float dy)
-    {
-        using Region region = new (new RectangleF (1, 2, 3, 4));
-        using Matrix matrix = new ();
-        region.Translate (dx, dy);
-        Assert.Equal ([new (1 + dx, 2 + dy, 3, 4)], region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Translate_IntInfinityIntersect_Success ()
-    {
-        using Region region = new ();
-        using Matrix matrix = new ();
-        region.Intersect (new Rectangle (-10, -10, 20, 20));
-        region.Translate (10, 10);
-
-        Assert.False (region.IsInfinite (s_graphic));
-        Assert.Equal ([new (0, 0, 20, 20)], region.GetRegionScans (matrix));
-    }
-
-    [Theory]
-    [InlineData (0, 0)]
-    [InlineData (2, 3)]
-    public void Translate_Float_Success (int dx, int dy)
-    {
-        using Region region = new (new RectangleF (1, 2, 3, 4));
-        using Matrix matrix = new ();
-        region.Translate (dx, dy);
-        Assert.Equal ([new (1 + dx, 2 + dy, 3, 4)], region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Translate_FloatInfinityIntersect_Success ()
-    {
-        using Region region = new ();
-        using Matrix matrix = new ();
-        region.Intersect (new Rectangle (-10, -10, 20, 20));
-        region.Translate (10f, 10f);
-
-        Assert.False (region.IsInfinite (s_graphic));
-        Assert.Equal ([new (0, 0, 20, 20)], region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Translate_Infinity_Nop ()
-    {
-        using Region region = new ();
-        using Matrix matrix = new ();
-        region.Translate (10, 10);
-        region.Translate (10f, 10f);
-
-        Assert.True (region.IsInfinite (s_graphic));
-        Assert.Equal ([new (-4194304, -4194304, 8388608, 8388608)], region.GetRegionScans (matrix));
-    }
-
-    [Theory]
-    [InlineData (float.MaxValue)]
-    [InlineData (float.MinValue)]
-    [InlineData (float.NaN)]
-    [InlineData (float.PositiveInfinity)]
-    [InlineData (float.NegativeInfinity)]
-    public void Translate_InvalidFloatValue_EmptiesRegion (float f)
-    {
-        using Region region = new (new RectangleF (1, 2, 3, 4));
-        using Matrix matrix = new ();
-        region.Translate (f, 0);
-
-        Assert.True (region.IsEmpty (s_graphic));
-        Assert.False (region.IsInfinite (s_graphic));
-        Assert.Empty (region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Translate_Disposed_ThrowsArgumentException ()
-    {
-        Region disposedRegion = CreateDisposedRegion ();
-
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Translate (1, 2));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Translate (1f, 2f));
-    }
-
-    public static IEnumerable<object []> Xor_TestData ()
-    {
-        yield return new object []
-        {
-            new Region (new RectangleF (500, 30, 60, 80)),
-            new RectangleF [] { new (500, 30, 60, 80) },
-            new RectangleF[0]
-        };
-
-        yield return new object []
-        {
-            new Region (new RectangleF (500, 30, 60, 80)),
-            new RectangleF [] { RectangleF.Empty },
-            new RectangleF [] { new (500, 30, 60, 80) }
-        };
-
-        yield return new object []
-        {
-            new Region (new RectangleF (0, 0, 0, 0)),
-            new RectangleF [] { new (500, 30, 60, 80) },
-            new RectangleF [] { new (500, 30, 60, 80) }
-        };
-
-        yield return new object []
-        {
-            new Region (),
-            new RectangleF [] { new (520, 40, 60, 80) },
-            new RectangleF []
-            {
-                new (-4194304, -4194304, 8388608, 4194344),
-                new (-4194304, 40, 4194824, 80),
-                new (580, 40, 4193724, 80),
-                new (-4194304, 120, 8388608, 4194184)
-            }
-        };
-
-        yield return new object []
-        {
-            new Region (),
-            new RectangleF [] { RectangleF.Empty },
-            new RectangleF [] { new Rectangle (-4194304, -4194304, 8388608, 8388608) }
-        };
-
-        yield return new object []
-        {
-            new Region (new RectangleF (380, 30, 60, 80)),
-            new RectangleF [] { new (410, 40, 60, 80) },
-            new RectangleF []
-            {
-                new (380, 30, 60, 10),
-                new (380, 40, 30, 70),
-                new (440, 40, 30, 70),
-                new (410, 110, 60, 10)
-            }
-        };
-    }
-
-    [Theory]
-    [MemberData (nameof (Xor_TestData))]
-    public void Xor_Region_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                using Region other = new (rect);
-                region.Xor (other);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Fact]
-    public void Xor_InfiniteRegion_Success ()
-    {
-        using Region region = new (new Rectangle (1, 2, 3, 4));
-        using Region other = new ();
-        using Matrix matrix = new ();
-        region.Xor (other);
-
-        Assert.Equal (
-                      [
-                          new (-4194304, -4194304, 8388608, 4194306),
-                          new (-4194304, 2, 4194305, 4),
-                          new (4, 2, 4194300, 4),
-                          new (-4194304, 6, 8388608, 4194298)
-                      ],
-                      region.GetRegionScans (matrix));
-    }
-
-    [Fact]
-    public void Xor_NullRegion_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("region", () => region.Xor ((Region)null));
-    }
-
-    [Fact]
-    public void Xor_DisposedRegion_ThrowsArgumentException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentException> (null, () => region.Xor (CreateDisposedRegion ()));
-    }
-
-    [Fact]
-    public void Xor_SameRegion_ThrowsInvalidOperationException ()
-    {
-        using Region region = new ();
-        Assert.Throws<InvalidOperationException> (() => region.Xor (region));
-    }
-
-    [Theory]
-    [MemberData (nameof (Xor_TestData))]
-    public void Xor_Rectangle_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                region.Xor (new Rectangle ((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height));
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Theory]
-    [MemberData (nameof (Xor_TestData))]
-    public void Xor_RectangleF_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                region.Xor (rect);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Theory]
-    [MemberData (nameof (Xor_TestData))]
-    public void Xor_GraphicsPath_Success (Region region, RectangleF [] rectangles, RectangleF [] expectedScans)
-    {
-        using (region)
-        {
-            foreach (RectangleF rect in rectangles)
-            {
-                using GraphicsPath path = new ();
-                path.AddRectangle (rect);
-                region.Xor (path);
-            }
-
-            using Matrix matrix = new ();
-            Assert.Equal (expectedScans, region.GetRegionScans (matrix));
-        }
-    }
-
-    [Fact]
-    public void Xor_EmptyPathWithInfiniteRegion_MakesInfinite ()
-    {
-        using Region region = new ();
-        using GraphicsPath graphicsPath = new ();
-        region.Xor (graphicsPath);
-        Assert.True (region.IsInfinite (s_graphic));
-    }
-
-    [Fact]
-    public void Xor_NullGraphicsPath_ThrowsArgumentNullException ()
-    {
-        using Region region = new ();
-        Assert.Throws<ArgumentNullException> ("path", () => region.Xor ((GraphicsPath)null));
-    }
-
-    [Fact]
-    public void Xor_Disposed_ThrowsArgumentException ()
-    {
-        Region disposedRegion = CreateDisposedRegion ();
-
-        using GraphicsPath graphicsPath = new ();
-        using Region other = new ();
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Xor (graphicsPath));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Xor (new Rectangle ()));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Xor (new RectangleF ()));
-        Assert.Throws<ArgumentException> (null, () => disposedRegion.Xor (other));
-    }
-
-}
-#endif

+ 36 - 0
UnitTests/View/ColorSchemeTests.cs

@@ -0,0 +1,36 @@
+#nullable enable
+using System.Text;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests;
+
+[Trait ("Category", "Output")]
+public class ColorSchemeTests (ITestOutputHelper _output)
+{
+
+
+    [Fact]
+    public void GetHotNormalColor_ColorScheme ()
+    {
+        var view = new View { ColorScheme = Colors.ColorSchemes ["Base"] };
+
+        Assert.Equal (view.ColorScheme.HotNormal, view.GetHotNormalColor ());
+
+        view.Enabled = false;
+        Assert.Equal (view.ColorScheme.Disabled, view.GetHotNormalColor ());
+        view.Dispose ();
+    }
+
+    [Fact]
+    public void GetNormalColor_ColorScheme ()
+    {
+        var view = new View { ColorScheme = Colors.ColorSchemes ["Base"] };
+
+        Assert.Equal (view.ColorScheme.Normal, view.GetNormalColor ());
+
+        view.Enabled = false;
+        Assert.Equal (view.ColorScheme.Disabled, view.GetNormalColor ());
+        view.Dispose ();
+    }
+
+}

+ 411 - 0
UnitTests/View/Draw/ClearViewportTests.cs

@@ -0,0 +1,411 @@
+#nullable enable
+using Moq;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests;
+
+[Trait ("Category", "Output")]
+public class ClearViewportTests (ITestOutputHelper _output)
+{
+    public class TestableView : View
+    {
+        public bool TestOnClearingViewport () { return OnClearingViewport (); }
+
+        public int OnClearingViewportCalled { get; set; } = 0;
+        public bool CancelOnClearingViewport { get; set;  }
+        protected override bool OnClearingViewport ()
+        {
+            OnClearingViewportCalled++;
+            return CancelOnClearingViewport;
+        }
+
+        public int OnClearedViewportCalled { get; set; } = 0;
+        protected override void OnClearedViewport ()
+        {
+            OnClearedViewportCalled++;
+        }
+    }
+
+    [Fact]
+    public void DoClearViewport_ViewportIsTransparent_DoesNotClear ()
+    {
+        // Arrange
+        Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
+        view.Object.ViewportSettings = ViewportSettings.Transparent;
+
+        // Act
+        view.Object.DoClearViewport ();
+
+        // Assert
+        Assert.Equal (0, view.Object.OnClearingViewportCalled);
+        Assert.Equal (0, view.Object.OnClearedViewportCalled);
+    }
+
+    [Fact]
+    public void DoClearViewport_OnClearingViewportReturnsTrue_DoesNotClear ()
+    {
+        // Arrange
+        Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
+        view.Object.CancelOnClearingViewport = true;
+
+        // Act
+        view.Object.DoClearViewport ();
+
+        // Assert
+        Assert.Equal (0, view.Object.OnClearedViewportCalled);
+    }
+
+    [Fact]
+    public void DoClearViewport_ClearingViewportEventCancelled_DoesNotClear ()
+    {
+        // Arrange
+        Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
+        view.Object.ClearingViewport += (sender, e) => e.Cancel = true;
+
+        // Act
+        view.Object.DoClearViewport ();
+
+        // Assert
+        Assert.Equal (0, view.Object.OnClearedViewportCalled);
+    }
+
+    [Fact]
+    public void DoClearViewport_ClearsViewport ()
+    {
+        // Arrange
+        Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
+
+        // Act
+        view.Object.DoClearViewport ();
+
+        // Assert
+        Assert.Equal (1, view.Object.OnClearedViewportCalled);
+    }
+
+    [Fact]
+    public void DoClearViewport_RaisesClearingViewportEvent ()
+    {
+        // Arrange
+        Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
+        var eventRaised = false;
+        view.Object.ClearingViewport += (sender, e) => eventRaised = true;
+
+        // Act
+        view.Object.DoClearViewport ();
+
+        // Assert
+        Assert.True (eventRaised);
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void Clear_ClearsEntireViewport ()
+    {
+        var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
+
+        var view = new View
+        {
+            Text = "X",
+            X = 1, Y = 1,
+            Width = 3, Height = 3,
+            BorderStyle = LineStyle.Single
+        };
+        superView.Add (view);
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.LayoutSubviews ();
+
+        superView.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      _output);
+
+        // On Draw exit the view is excluded from the clip, so this will do nothing.
+        view.ClearViewport ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      _output);
+
+        View.SetClipToScreen ();
+
+        view.ClearViewport ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │ │
+ └─┘",
+                                                      _output);
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly ()
+    {
+        var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
+
+        var view = new View
+        {
+            Text = "X",
+            X = 1, Y = 1,
+            Width = 3, Height = 3,
+            BorderStyle = LineStyle.Single,
+            ViewportSettings = ViewportSettings.ClearContentOnly
+        };
+        superView.Add (view);
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.LayoutSubviews ();
+
+        superView.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      _output);
+        View.SetClipToScreen ();
+        view.ClearViewport ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │ │
+ └─┘",
+                                                      _output);
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void Clear_Viewport_Can_Use_Driver_AddRune_Or_AddStr_Methods ()
+    {
+        var view = new FrameView { Width = Dim.Fill (), Height = Dim.Fill () };
+
+        view.DrawingContent += (s, e) =>
+                               {
+                                   Region savedClip = view.AddViewportToClip ();
+
+                                   for (var row = 0; row < view.Viewport.Height; row++)
+                                   {
+                                       Application.Driver?.Move (1, row + 1);
+
+                                       for (var col = 0; col < view.Viewport.Width; col++)
+                                       {
+                                           Application.Driver?.AddStr ($"{col}");
+                                       }
+                                   }
+
+                                   View.SetClip (savedClip);
+                                   e.Cancel = true;
+                               };
+        var top = new Toplevel ();
+        top.Add (view);
+        Application.Begin (top);
+        ((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
+
+        var expected = @"
+┌──────────────────┐
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+└──────────────────┘
+"
+            ;
+
+        Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        Assert.Equal (new (0, 0, 20, 10), pos);
+
+        view.FillRect (view.Viewport);
+
+        expected = @"
+┌──────────────────┐
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+└──────────────────┘
+"
+            ;
+
+        pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        top.Dispose ();
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void Clear_Can_Use_Driver_AddRune_Or_AddStr_Methods ()
+    {
+        var view = new FrameView { Width = Dim.Fill (), Height = Dim.Fill () };
+
+        view.DrawingContent += (s, e) =>
+                               {
+                                   Region savedClip = view.AddViewportToClip ();
+
+                                   for (var row = 0; row < view.Viewport.Height; row++)
+                                   {
+                                       Application.Driver?.Move (1, row + 1);
+
+                                       for (var col = 0; col < view.Viewport.Width; col++)
+                                       {
+                                           Application.Driver?.AddStr ($"{col}");
+                                       }
+                                   }
+
+                                   View.SetClip (savedClip);
+                                   e.Cancel = true;
+                               };
+        var top = new Toplevel ();
+        top.Add (view);
+        Application.Begin (top);
+        ((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
+
+        var expected = @"
+┌──────────────────┐
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+│012345678910111213│
+└──────────────────┘
+"
+            ;
+
+        Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        Assert.Equal (new (0, 0, 20, 10), pos);
+
+        view.FillRect (view.Viewport);
+
+        expected = @"
+┌──────────────────┐
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+└──────────────────┘
+";
+
+        pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+        top.Dispose ();
+    }
+
+    [Theory]
+    [AutoInitShutdown (configLocation: ConfigLocations.Default)]
+    [InlineData (true)]
+    [InlineData (false)]
+    public void Clear_Does_Not_Spillover_Its_Parent (bool label)
+    {
+        var root = new View { Width = 20, Height = 10, ColorScheme = Colors.ColorSchemes ["Base"] };
+
+        string text = new ('c', 100);
+
+        View v = label
+
+                     // Label has Width/Height == AutoSize, so Frame.Size will be (100, 1)
+                     ? new Label { Text = text }
+
+                     // TextView has Width/Height == (Dim.Fill, 1), so Frame.Size will be 20 (width of root), 1
+                     : new TextView { Width = Dim.Fill (), Height = 1, Text = text };
+
+        root.Add (v);
+
+        var top = new Toplevel ();
+        top.Add (root);
+        RunState runState = Application.Begin (top);
+        Application.RunIteration (ref runState);
+
+        if (label)
+        {
+            Assert.False (v.CanFocus);
+            Assert.Equal (new (0, 0, text.Length, 1), v.Frame);
+        }
+        else
+        {
+            Assert.True (v.CanFocus);
+            Assert.Equal (new (0, 0, 20, 1), v.Frame);
+        }
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+cccccccccccccccccccc",
+                                                      _output
+                                                     );
+
+        Attribute [] attributes =
+        {
+            Colors.ColorSchemes ["TopLevel"].Normal,
+            Colors.ColorSchemes ["Base"].Normal,
+            Colors.ColorSchemes ["Base"].Focus
+        };
+
+        if (label)
+        {
+            TestHelpers.AssertDriverAttributesAre (
+                                                   @"
+111111111111111111110
+111111111111111111110",
+                                                   _output,
+                                                   Application.Driver,
+                                                   attributes
+                                                  );
+        }
+        else
+        {
+            TestHelpers.AssertDriverAttributesAre (
+                                                   @"
+222222222222222222220
+111111111111111111110",
+                                                   _output,
+                                                   Application.Driver,
+                                                   attributes
+                                                  );
+        }
+
+        if (label)
+        {
+            root.CanFocus = true;
+            v.CanFocus = true;
+            Assert.True (v.HasFocus);
+            v.SetFocus ();
+            Assert.True (v.HasFocus);
+            Application.LayoutAndDraw ();
+
+            TestHelpers.AssertDriverAttributesAre (
+                                                   @"
+222222222222222222220
+111111111111111111110",
+                                                   _output,
+                                                   Application.Driver,
+                                                   attributes
+                                                  );
+        }
+
+        Application.End (runState);
+        top.Dispose ();
+    }
+}

+ 303 - 0
UnitTests/View/Draw/ClipTests.cs

@@ -0,0 +1,303 @@
+#nullable enable
+using System.Text;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests;
+
+[Trait ("Category", "Output")]
+public class ClipTests (ITestOutputHelper _output)
+{
+    [Fact]
+    [SetupFakeDriver]
+    public void Move_Is_Not_Constrained_To_Viewport ()
+    {
+        var view = new View
+        {
+            X = 1,
+            Y = 1,
+            Width = 3, Height = 3
+        };
+        view.Margin!.Thickness = new (1);
+
+        view.Move (0, 0);
+        Assert.Equal (new (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row));
+
+        view.Move (-1, -1);
+        Assert.Equal (new (1, 1), new Point (Application.Driver!.Col, Application.Driver!.Row));
+
+        view.Move (1, 1);
+        Assert.Equal (new (3, 3), new Point (Application.Driver!.Col, Application.Driver!.Row));
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void AddRune_Is_Constrained_To_Viewport ()
+    {
+        var view = new View
+        {
+            X = 1,
+            Y = 1,
+            Width = 3, Height = 3
+        };
+        view.Padding!.Thickness = new (1);
+        view.Padding.Diagnostics = ViewDiagnosticFlags.Thickness;
+        view.BeginInit ();
+        view.EndInit ();
+        view.Draw ();
+
+        // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen)
+        Assert.Equal ((Rune)' ', Application.Driver?.Contents! [2, 2].Rune);
+
+        // When we exit Draw, the view is excluded from the clip. So drawing at 0,0, is not valid and is clipped.
+        view.AddRune (0, 0, Rune.ReplacementChar);
+        Assert.Equal ((Rune)' ', Application.Driver?.Contents! [2, 2].Rune);
+
+        view.AddRune (-1, -1, Rune.ReplacementChar);
+        Assert.Equal ((Rune)'P', Application.Driver?.Contents! [1, 1].Rune);
+
+        view.AddRune (1, 1, Rune.ReplacementChar);
+        Assert.Equal ((Rune)'P', Application.Driver?.Contents! [3, 3].Rune);
+    }
+
+    [Theory]
+    [InlineData (0, 0, 1, 1)]
+    [InlineData (0, 0, 2, 2)]
+    [InlineData (-1, -1, 2, 2)]
+    [SetupFakeDriver]
+    public void FillRect_Fills_HonorsClip (int x, int y, int width, int height)
+    {
+        var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
+
+        var view = new View
+        {
+            Text = "X",
+            X = 1, Y = 1,
+            Width = 3, Height = 3,
+            BorderStyle = LineStyle.Single
+        };
+        superView.Add (view);
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.LayoutSubviews ();
+
+        superView.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      _output);
+
+        Rectangle toFill = new (x, y, width, height);
+        View.SetClipToScreen ();
+        view.FillRect (toFill);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │ │
+ └─┘",
+                                                      _output);
+
+        // Now try to clear beyond Viewport (invalid; clipping should prevent)
+        superView.SetNeedsDraw ();
+        superView.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      _output);
+        toFill = new (-width, -height, width, height);
+        view.FillRect (toFill);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      _output);
+
+        // Now try to clear beyond Viewport (valid)
+        superView.SetNeedsDraw ();
+        superView.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      _output);
+        toFill = new (-1, -1, width + 1, height + 1);
+
+        View.SetClipToScreen ();
+        view.FillRect (toFill);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │ │
+ └─┘",
+                                                      _output);
+
+        // Now clear too much size
+        superView.SetNeedsDraw ();
+        superView.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │X│
+ └─┘",
+                                                      _output);
+        toFill = new (0, 0, width * 2, height * 2);
+        View.SetClipToScreen ();
+        view.FillRect (toFill);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+ ┌─┐
+ │ │
+ └─┘",
+                                                      _output);
+    }
+
+    // TODO: Simplify this test to just use AddRune directly
+    [Fact]
+    [SetupFakeDriver]
+    [Trait ("Category", "Unicode")]
+    public void Clipping_Wide_Runes ()
+    {
+        ((FakeDriver)Application.Driver!).SetBufferSize (30, 1);
+
+        var top = new View
+        {
+            Id = "top",
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+        };
+
+        var frameView = new View
+        {
+            Id = "frameView",
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+            Text = """
+                   これは広いルーンラインです。
+                   """
+        };
+        frameView.Border!.LineStyle = LineStyle.Single;
+        frameView.Border.Thickness = new (1, 0, 0, 0);
+
+        top.Add (frameView);
+        View.SetClipToScreen ();
+        top.Layout ();
+        top.Draw ();
+
+        var expectedOutput = """
+                             │これは広いルーンラインです。
+                             """;
+
+        TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
+
+        var view = new View
+        {
+            Text = "0123456789",
+
+            //Text = "ワイドルー。",
+            X = 2,
+            Height = Dim.Auto (),
+            Width = Dim.Auto (),
+            BorderStyle = LineStyle.Single
+        };
+        view.Border!.Thickness = new (1, 0, 1, 0);
+
+        top.Add (view);
+        top.Layout ();
+        View.SetClipToScreen ();
+        top.Draw ();
+
+        //                            012345678901234567890123456789012345678
+        //                            012 34 56 78 90 12 34 56 78 90 12 34 56 78
+        //                            │こ れ  は 広 い  ル ー ン  ラ イ ン で  す 。
+        //                            01 2345678901234 56 78 90 12 34 56 
+        //                            │� |0123456989│� ン  ラ イ ン で  す 。
+        expectedOutput = """
+                         │�│0123456789│�ンラインです。
+                         """;
+
+        TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
+    }
+
+    // TODO: Add more AddRune tests to cover all the cases where wide runes are clipped
+
+    [Fact]
+    [SetupFakeDriver]
+    public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped ()
+    {
+        // Screen is 25x25
+        // View is 25x25
+        // Viewport is (0, 0, 23, 23)
+        // ContentSize is (10, 10)
+        // ViewportToScreen is (1, 1, 23, 23)
+        // Visible content is (1, 1, 10, 10)
+        // Expected clip is (1, 1, 10, 10) - same as visible content
+        Rectangle expectedClip = new (1, 1, 10, 10);
+
+        // Arrange
+        var view = new View
+        {
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+            ViewportSettings = ViewportSettings.ClipContentOnly
+        };
+        view.SetContentSize (new Size (10, 10));
+        view.Border!.Thickness = new (1);
+        view.BeginInit ();
+        view.EndInit ();
+        Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ());
+
+        // Act
+        view.AddViewportToClip ();
+
+        // Assert
+        Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ());
+        view.Dispose ();
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void SetClip_Default_ClipsToViewport ()
+    {
+        // Screen is 25x25
+        // View is 25x25
+        // Viewport is (0, 0, 23, 23)
+        // ContentSize is (10, 10)
+        // ViewportToScreen is (1, 1, 23, 23)
+        // Visible content is (1, 1, 10, 10)
+        // Expected clip is (1, 1, 23, 23) - same as Viewport
+        Rectangle expectedClip = new (1, 1, 23, 23);
+
+        // Arrange
+        var view = new View
+        {
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+        };
+        view.SetContentSize (new Size (10, 10));
+        view.Border!.Thickness = new (1);
+        view.BeginInit ();
+        view.EndInit ();
+        Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ());
+        view.Viewport = view.Viewport with { X = 1, Y = 1 };
+
+        // Act
+        view.AddViewportToClip ();
+
+        // Assert
+        Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ());
+        view.Dispose ();
+    }
+}

+ 32 - 0
UnitTests/View/Draw/DrawEventTests.cs

@@ -0,0 +1,32 @@
+#nullable enable
+using System.Text;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests;
+
+[Trait ("Category", "Output")]
+public class DrawEventTests (ITestOutputHelper _output)
+{
+
+    [Fact]
+    [AutoInitShutdown]
+    public void DrawContentComplete_Event_Is_Always_Called ()
+    {
+        var viewCalled = false;
+        var tvCalled = false;
+
+        var view = new View { Width = 10, Height = 10, Text = "View" };
+        view.DrawComplete += (s, e) => viewCalled = true;
+        var tv = new TextView { Y = 11, Width = 10, Height = 10 };
+        tv.DrawComplete += (s, e) => tvCalled = true;
+
+        var top = new Toplevel ();
+        top.Add (view, tv);
+        RunState runState = Application.Begin (top);
+        Application.RunIteration (ref runState);
+
+        Assert.True (viewCalled);
+        Assert.True (tvCalled);
+        top.Dispose ();
+    }
+}

+ 263 - 376
UnitTests/View/Draw/DrawTests.cs

@@ -1,5 +1,6 @@
 #nullable enable
 using System.Text;
+using Microsoft.VisualStudio.TestPlatform.Utilities;
 using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewTests;
@@ -8,248 +9,6 @@ namespace Terminal.Gui.ViewTests;
 public class DrawTests (ITestOutputHelper _output)
 {
 
-    [Fact]
-    [SetupFakeDriver]
-    public void Move_Is_Not_Constrained_To_Viewport ()
-    {
-        var view = new View
-        {
-            X = 1,
-            Y = 1,
-            Width = 3, Height = 3
-        };
-        view.Margin!.Thickness = new (1);
-
-        view.Move (0, 0);
-        Assert.Equal (new (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row));
-
-        view.Move (-1, -1);
-        Assert.Equal (new (1, 1), new Point (Application.Driver!.Col, Application.Driver!.Row));
-
-        view.Move (1, 1);
-        Assert.Equal (new (3, 3), new Point (Application.Driver!.Col, Application.Driver!.Row));
-    }
-
-    [Fact]
-    [SetupFakeDriver]
-    public void AddRune_Is_Constrained_To_Viewport ()
-    {
-        var view = new View
-        {
-            X = 1,
-            Y = 1,
-            Width = 3, Height = 3
-        };
-        view.Padding!.Thickness = new (1);
-        view.Padding.Diagnostics = ViewDiagnosticFlags.Thickness;
-        view.BeginInit ();
-        view.EndInit ();
-        view.Draw ();
-
-        // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen)
-        Assert.Equal ((Rune)' ', Application.Driver?.Contents! [2, 2].Rune);
-
-        // When we exit Draw, the view is excluded from the clip. So drawing at 0,0, is not valid and is clipped.
-        view.AddRune (0, 0, Rune.ReplacementChar);
-        Assert.Equal ((Rune)' ', Application.Driver?.Contents! [2, 2].Rune);
-
-        view.AddRune (-1, -1, Rune.ReplacementChar);
-        Assert.Equal ((Rune)'P', Application.Driver?.Contents! [1, 1].Rune);
-
-        view.AddRune (1, 1, Rune.ReplacementChar);
-        Assert.Equal ((Rune)'P', Application.Driver?.Contents! [3, 3].Rune);
-    }
-
-    [Theory]
-    [InlineData (0, 0, 1, 1)]
-    [InlineData (0, 0, 2, 2)]
-    [InlineData (-1, -1, 2, 2)]
-    [SetupFakeDriver]
-    public void FillRect_Fills_HonorsClip (int x, int y, int width, int height)
-    {
-        var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
-
-        var view = new View
-        {
-            Text = "X",
-            X = 1, Y = 1,
-            Width = 3, Height = 3,
-            BorderStyle = LineStyle.Single
-        };
-        superView.Add (view);
-        superView.BeginInit ();
-        superView.EndInit ();
-        superView.LayoutSubviews ();
-
-        superView.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │X│
- └─┘",
-                                                      _output);
-
-        Rectangle toFill = new (x, y, width, height);
-        View.SetClipToScreen ();
-        view.FillRect (toFill);
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │ │
- └─┘",
-                                                      _output);
-
-        // Now try to clear beyond Viewport (invalid; clipping should prevent)
-        superView.SetNeedsDraw ();
-        superView.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │X│
- └─┘",
-                                                      _output);
-        toFill = new (-width, -height, width, height);
-        view.FillRect (toFill);
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │X│
- └─┘",
-                                                      _output);
-
-        // Now try to clear beyond Viewport (valid)
-        superView.SetNeedsDraw ();
-        superView.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │X│
- └─┘",
-                                                      _output);
-        toFill = new (-1, -1, width + 1, height + 1);
-
-        View.SetClipToScreen ();
-        view.FillRect (toFill);
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │ │
- └─┘",
-                                                      _output);
-
-        // Now clear too much size
-        superView.SetNeedsDraw ();
-        superView.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │X│
- └─┘",
-                                                      _output);
-        toFill = new (0, 0, width * 2, height * 2);
-        View.SetClipToScreen ();
-        view.FillRect (toFill);
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │ │
- └─┘",
-                                                      _output);
-    }
-
-    [Fact]
-    [SetupFakeDriver]
-    public void Clear_ClearsEntireViewport ()
-    {
-        var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
-
-        var view = new View
-        {
-            Text = "X",
-            X = 1, Y = 1,
-            Width = 3, Height = 3,
-            BorderStyle = LineStyle.Single
-        };
-        superView.Add (view);
-        superView.BeginInit ();
-        superView.EndInit ();
-        superView.LayoutSubviews ();
-
-        superView.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │X│
- └─┘",
-                                                      _output);
-
-        // On Draw exit the view is excluded from the clip, so this will do nothing.
-        view.ClearViewport ();
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │X│
- └─┘",
-                                                      _output);
-
-        View.SetClipToScreen ();
-
-        view.ClearViewport ();
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │ │
- └─┘",
-                                                      _output);
-    }
-
-    [Fact]
-    [SetupFakeDriver]
-    public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly ()
-    {
-        var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
-
-        var view = new View
-        {
-            Text = "X",
-            X = 1, Y = 1,
-            Width = 3, Height = 3,
-            BorderStyle = LineStyle.Single,
-            ViewportSettings = ViewportSettings.ClearContentOnly
-        };
-        superView.Add (view);
-        superView.BeginInit ();
-        superView.EndInit ();
-        superView.LayoutSubviews ();
-
-        superView.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │X│
- └─┘",
-                                                      _output);
-        View.SetClipToScreen ();
-        view.ClearViewport ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
- ┌─┐
- │ │
- └─┘",
-                                                      _output);
-    }
-
     [Fact]
     [AutoInitShutdown]
     [Trait ("Category", "Unicode")]
@@ -290,72 +49,6 @@ public class DrawTests (ITestOutputHelper _output)
         top.Dispose ();
     }
 
-    // TODO: Simplify this test to just use AddRune directly
-    [Fact]
-    [SetupFakeDriver]
-    [Trait ("Category", "Unicode")]
-    public void Clipping_Wide_Runes ()
-    {
-        ((FakeDriver)Application.Driver!).SetBufferSize (30, 1);
-
-        var top = new View ()
-        {
-            Id = "top",
-            Width = Dim.Fill (),
-            Height = Dim.Fill (),
-        };
-        var frameView = new View ()
-        {
-            Id = "frameView",
-            Width = Dim.Fill (),
-            Height = Dim.Fill (),
-            Text = """
-                   これは広いルーンラインです。
-                   """,
-        };
-        frameView.Border!.LineStyle = LineStyle.Single;
-        frameView.Border.Thickness = new (1, 0, 0, 0);
-
-        top.Add (frameView);
-        View.SetClipToScreen ();
-        top.Layout ();
-        top.Draw ();
-
-        string expectedOutput = """
-                                      │これは広いルーンラインです。
-                                      """;
-
-        TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
-
-        var view = new View
-        {
-            Text = "0123456789",
-            //Text = "ワイドルー。",
-            X = 2,
-            Height = Dim.Auto (),
-            Width = Dim.Auto (),
-            BorderStyle = LineStyle.Single
-        };
-        view.Border!.Thickness = new (1, 0, 1, 0);
-
-        top.Add (view);
-        top.Layout ();
-        View.SetClipToScreen ();
-        top.Draw ();
-        //                            012345678901234567890123456789012345678
-        //                            012 34 56 78 90 12 34 56 78 90 12 34 56 78
-        //                            │こ れ  は 広 い  ル ー ン  ラ イ ン で  す 。
-        //                            01 2345678901234 56 78 90 12 34 56 
-        //                            │� |0123456989│� ン  ラ イ ン で  す 。
-        expectedOutput = """
-                         │�│0123456789│�ンラインです。
-                         """;
-
-        TestHelpers.AssertDriverContentsWithFrameAre (expectedOutput, _output);
-    }
-
-    // TODO: Add more AddRune tests to cover all the cases where wide runes are clipped
-
     [Fact]
     [AutoInitShutdown]
     [Trait ("Category", "Output")]
@@ -941,74 +634,6 @@ public class DrawTests (ITestOutputHelper _output)
         // This test has nothing to do with color - removing as it is not relevant and fragile
     }
 
-    [Fact]
-    [SetupFakeDriver]
-    public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped ()
-    {
-        // Screen is 25x25
-        // View is 25x25
-        // Viewport is (0, 0, 23, 23)
-        // ContentSize is (10, 10)
-        // ViewportToScreen is (1, 1, 23, 23)
-        // Visible content is (1, 1, 10, 10)
-        // Expected clip is (1, 1, 10, 10) - same as visible content
-        Rectangle expectedClip = new (1, 1, 10, 10);
-
-        // Arrange
-        var view = new View
-        {
-            Width = Dim.Fill (),
-            Height = Dim.Fill (),
-            ViewportSettings = ViewportSettings.ClipContentOnly
-        };
-        view.SetContentSize (new Size (10, 10));
-        view.Border!.Thickness = new (1);
-        view.BeginInit ();
-        view.EndInit ();
-        Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ());
-
-        // Act
-        view.ClipViewport ();
-
-        // Assert
-        Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ());
-        view.Dispose ();
-    }
-
-    [Fact]
-    [SetupFakeDriver]
-    public void SetClip_Default_ClipsToViewport ()
-    {
-        // Screen is 25x25
-        // View is 25x25
-        // Viewport is (0, 0, 23, 23)
-        // ContentSize is (10, 10)
-        // ViewportToScreen is (1, 1, 23, 23)
-        // Visible content is (1, 1, 10, 10)
-        // Expected clip is (1, 1, 23, 23) - same as Viewport
-        Rectangle expectedClip = new (1, 1, 23, 23);
-
-        // Arrange
-        var view = new View
-        {
-            Width = Dim.Fill (),
-            Height = Dim.Fill ()
-        };
-        view.SetContentSize (new Size (10, 10));
-        view.Border!.Thickness = new (1);
-        view.BeginInit ();
-        view.EndInit ();
-        Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ());
-        view.Viewport = view.Viewport with { X = 1, Y = 1 };
-
-        // Act
-        view.ClipViewport ();
-
-        // Assert
-        Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ());
-        view.Dispose ();
-    }
-
     [Fact]
     [TestRespondersDisposed]
     public void Draw_Throws_IndexOutOfRangeException_With_Negative_Bounds ()
@@ -1042,4 +667,266 @@ public class DrawTests (ITestOutputHelper _output)
         // Shutdown must be called to safely clean up Application if Init has been called
         Application.Shutdown ();
     }
+
+
+    [Fact]
+    [AutoInitShutdown]
+    public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Frame ()
+    {
+        var label = new Label { Text = "At 0,0" };
+
+        var view = new DerivedView
+        {
+            X = 2,
+            Y = 2,
+            Width = 30,
+            Height = 2,
+            Text = "A text with some long width\n and also with two lines."
+        };
+        Toplevel top = new ();
+        top.Add (label, view);
+        RunState runState = Application.Begin (top);
+        Application.RunIteration (ref runState);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  "
+        ,
+                                                      _output
+                                                     );
+
+        view.Frame = new (3, 3, 10, 1);
+        Assert.Equal (new (3, 3, 10, 1), view.Frame);
+        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
+        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
+        //Application.Refresh();
+        top.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+        @"
+At 0,0       
+             
+             
+   A text wit",
+                                                      _output
+                                                     );
+        Application.End (runState);
+        top.Dispose ();
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Pos_Dim ()
+    {
+        var label = new Label { Text = "At 0,0" };
+
+        var view = new DerivedView
+        {
+            X = 2,
+            Y = 2,
+            Width = 30,
+            Height = 2,
+            Text = "A text with some long width\n and also with two lines."
+        };
+        Toplevel top = new ();
+        top.Add (label, view);
+        RunState runState = Application.Begin (top);
+
+        top.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  "
+        ,
+                                                      _output
+                                                     );
+
+        view.X = 3;
+        view.Y = 3;
+        view.Width = 10;
+        view.Height = 1;
+        Assert.Equal (new (3, 3, 10, 1), view.Frame);
+        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
+        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
+        View.SetClipToScreen ();
+        top.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+At 0,0       
+             
+             
+   A text wit"
+        ,
+                                                      _output
+                                                     );
+        Application.End (runState);
+        top.Dispose ();
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Frame ()
+    {
+        var label = new Label { Text = "At 0,0" };
+
+        var view = new DerivedView
+        {
+            X = 2,
+            Y = 2,
+            Width = 30,
+            Height = 2,
+            Text = "A text with some long width\n and also with two lines."
+        };
+        Toplevel top = new ();
+        top.Add (label, view);
+        RunState runState = Application.Begin (top);
+        Application.RunIteration (ref runState);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  "
+        ,
+                                                      _output
+                                                     );
+
+        view.Frame = new (1, 1, 10, 1);
+        Assert.Equal (new (1, 1, 10, 1), view.Frame);
+        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
+        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
+        top.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+        @"
+At 0,0     
+ A text wit"
+        ,
+                                                      _output
+                                                     );
+        Application.End (runState);
+        top.Dispose ();
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Pos_Dim ()
+    {
+        var label = new Label { Text = "At 0,0" };
+
+        var view = new DerivedView
+        {
+            X = 2,
+            Y = 2,
+            Width = 30,
+            Height = 2,
+            Text = "A text with some long width\n and also with two lines."
+        };
+        Toplevel top = new ();
+        top.Add (label, view);
+        RunState runState = Application.Begin (top);
+
+        top.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+At 0,0                       
+                             
+  A text with some long width
+   and also with two lines.  "
+        ,
+                                                      _output
+                                                     );
+
+        view.X = 1;
+        view.Y = 1;
+        view.Width = 10;
+        view.Height = 1;
+        Assert.Equal (new (1, 1, 10, 1), view.Frame);
+        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
+        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
+        View.SetClipToScreen ();
+
+        top.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
+At 0,0     
+ A text wit"
+        ,
+                                                      _output
+                                                     );
+        Application.End (runState);
+        top.Dispose ();
+    }
+    public class DerivedView : View
+    {
+        public DerivedView () { CanFocus = true; }
+        public bool IsKeyDown { get; set; }
+        public bool IsKeyPress { get; set; }
+        public bool IsKeyUp { get; set; }
+        public override string Text { get; set; }
+
+        protected override bool OnDrawingContent ()
+        {
+            var idx = 0;
+
+            // BUGBUG: v2 - this should use Viewport, not Frame
+            for (var r = 0; r < Frame.Height; r++)
+            {
+                for (var c = 0; c < Frame.Width; c++)
+                {
+                    if (idx < Text.Length)
+                    {
+                        char rune = Text [idx];
+
+                        if (rune != '\n')
+                        {
+                            AddRune (c, r, (Rune)Text [idx]);
+                        }
+
+                        idx++;
+
+                        if (rune == '\n')
+                        {
+                            break;
+                        }
+                    }
+                }
+            }
+
+            ClearNeedsDraw ();
+
+            return true;
+        }
+
+        protected override bool OnKeyDown (Key keyEvent)
+        {
+            IsKeyDown = true;
+
+            return true;
+        }
+
+        public override bool OnKeyUp (Key keyEvent)
+        {
+            IsKeyUp = true;
+
+            return true;
+        }
+
+        protected override bool OnKeyDownNotHandled (Key keyEvent)
+        {
+            IsKeyPress = true;
+
+            return true;
+        }
+    }
 }

+ 60 - 0
UnitTests/View/Draw/NeedsDisplayTests.cs → UnitTests/View/Draw/NeedsDrawTests.cs

@@ -296,4 +296,64 @@ public class NeedsDrawTests ()
         Assert.Equal (new (1, 1, 5, 5), view._needsDrawRect);
 
     }
+
+
+    [Fact]
+    [AutoInitShutdown]
+    public void Frame_Set_After_Initialize_Update_NeededDisplay ()
+    {
+        var frame = new FrameView ();
+
+        var label = new Label
+        {
+            ColorScheme = Colors.ColorSchemes ["Menu"], X = 0, Y = 0, Text = "This should be the first line."
+        };
+
+        var view = new View
+        {
+            X = 0, // don't overcomplicate unit tests
+            Y = 1,
+            Height = Dim.Auto (DimAutoStyle.Text),
+            Width = Dim.Auto (DimAutoStyle.Text),
+            Text = "Press me!"
+        };
+
+        frame.Add (label, view);
+
+        frame.X = Pos.Center ();
+        frame.Y = Pos.Center ();
+        frame.Width = 40;
+        frame.Height = 8;
+
+        Toplevel top = new ();
+
+        top.Add (frame);
+
+        RunState runState = Application.Begin (top);
+
+        top.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 80, 25), top._needsDrawRect); };
+
+        frame.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 40, 8), frame._needsDrawRect); };
+
+        label.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 38, 1), label._needsDrawRect); };
+
+        view.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 13, 1), view._needsDrawRect); };
+
+        Assert.Equal (new (0, 0, 80, 25), top.Frame);
+        Assert.Equal (new (20, 8, 40, 8), frame.Frame);
+
+        Assert.Equal (
+                      new (20, 8, 60, 16),
+                      new Rectangle (
+                                     frame.Frame.Left,
+                                     frame.Frame.Top,
+                                     frame.Frame.Right,
+                                     frame.Frame.Bottom
+                                    )
+                     );
+        Assert.Equal (new (0, 0, 30, 1), label.Frame);
+        Assert.Equal (new (0, 1, 9, 1), view.Frame); // this proves frame was set
+        Application.End (runState);
+        top.Dispose ();
+    }
 }

+ 107 - 0
UnitTests/View/Draw/TransparentTests.cs

@@ -0,0 +1,107 @@
+#nullable enable
+using System.Text;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests;
+
+[Trait ("Category", "Output")]
+public class TransparentTests (ITestOutputHelper _output)
+{
+    [Fact]
+    [SetupFakeDriver]
+
+    public void Transparent_Text_Occludes ()
+    {
+        var super = new View
+        {
+            Id = "super",
+            Width = 20,
+            Height = 5,
+        };
+        super.DrawingContent += (sender, args) =>
+                                {
+                                    var s = sender as View;
+                                    s!.FillRect(s!.Viewport, Glyphs.Stipple);
+                                    args.Cancel = true;
+                                };
+
+        var sub = new View
+        {
+            X = 1,
+            Y = 1,
+            Width = 15,
+            Height = 3,
+            Id = "sub",
+            Text = "Sub",
+            ViewportSettings = ViewportSettings.Transparent,
+            BorderStyle = LineStyle.Single
+        };
+
+        super.Add (sub);
+
+        super.Layout ();
+        super.Draw ();
+
+        _ = TestHelpers.AssertDriverContentsWithFrameAre (
+                                                        @"
+░░░░░░░░░░░░░░░░░░░░
+░┌─────────────┐░░░░
+░│Sub░░░░░░░░░░│░░░░
+░└─────────────┘░░░░
+░░░░░░░░░░░░░░░░░░░░", _output);
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+
+    public void Transparent_Subview_Occludes ()
+    {
+        var super = new View
+        {
+            Id = "super",
+            Width = 20,
+            Height = 5,
+        };
+        super.DrawingContent += (sender, args) =>
+                                {
+                                    var s = sender as View;
+                                    s!.FillRect (s!.Viewport, Glyphs.Stipple);
+                                    args.Cancel = true;
+                                };
+
+        var sub = new View
+        {
+            X = 1,
+            Y = 1,
+            Width = 15,
+            Height = 3,
+            Id = "sub",
+            ViewportSettings = ViewportSettings.Transparent,
+            BorderStyle = LineStyle.Single
+        };
+
+        var subSub = new View
+        {
+            X = Pos.Center(),
+            Y = Pos.Center(),
+            Width = Dim.Auto(),
+            Height = Dim.Auto(),
+            Id = "subSub",
+            Text = "subSub",
+        };
+        sub.Add (subSub);
+
+        super.Add (sub);
+
+        super.Layout ();
+        super.Draw ();
+
+        _ = TestHelpers.AssertDriverContentsWithFrameAre (
+                                                          @"
+░░░░░░░░░░░░░░░░░░░░
+░┌─────────────┐░░░░
+░│░░░subSub░░░░│░░░░
+░└─────────────┘░░░░
+░░░░░░░░░░░░░░░░░░░░", _output);
+    }
+}

+ 15 - 0
UnitTests/View/TextTests.cs

@@ -1299,4 +1299,19 @@ w ";
 
         pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
     }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void SetText_RendersCorrectly ()
+    {
+        View view;
+        var text = "test";
+
+        view = new Label { Text = text };
+        view.BeginInit ();
+        view.EndInit ();
+        view.Draw ();
+
+        TestHelpers.AssertDriverContentsWithFrameAre (text, output);
+    }
 }

+ 1 - 783
UnitTests/View/ViewTests.cs

@@ -155,418 +155,6 @@ public class ViewTests (ITestOutputHelper output)
 #endif
     }
 
-    [Fact]
-    [AutoInitShutdown]
-    public void Clear_Viewport_Can_Use_Driver_AddRune_Or_AddStr_Methods ()
-    {
-        var view = new FrameView { Width = Dim.Fill (), Height = Dim.Fill () };
-
-        view.DrawingContent += (s, e) =>
-                            {
-                                Region savedClip = view.ClipViewport ();
-
-                                for (var row = 0; row < view.Viewport.Height; row++)
-                                {
-                                    Application.Driver?.Move (1, row + 1);
-
-                                    for (var col = 0; col < view.Viewport.Width; col++)
-                                    {
-                                        Application.Driver?.AddStr ($"{col}");
-                                    }
-                                }
-
-                                View.SetClip (savedClip);
-                                e.Cancel = true;
-                            };
-        var top = new Toplevel ();
-        top.Add (view);
-        Application.Begin (top);
-        ((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
-
-        var expected = @"
-┌──────────────────┐
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-└──────────────────┘
-";
-
-        Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-        Assert.Equal (new (0, 0, 20, 10), pos);
-
-        view.FillRect (view.Viewport);
-
-        expected = @"
-┌──────────────────┐
-│                  │
-│                  │
-│                  │
-│                  │
-│                  │
-│                  │
-│                  │
-│                  │
-└──────────────────┘
-";
-
-        pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-        top.Dispose ();
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Clear_Can_Use_Driver_AddRune_Or_AddStr_Methods ()
-    {
-        var view = new FrameView { Width = Dim.Fill (), Height = Dim.Fill () };
-
-        view.DrawingContent += (s, e) =>
-                               {
-                                   Region savedClip = view.ClipViewport ();
-
-                                   for (var row = 0; row < view.Viewport.Height; row++)
-                                   {
-                                       Application.Driver?.Move (1, row + 1);
-
-                                       for (var col = 0; col < view.Viewport.Width; col++)
-                                       {
-                                           Application.Driver?.AddStr ($"{col}");
-                                       }
-                                   }
-
-                                   View.SetClip (savedClip);
-                                   e.Cancel = true;
-                               };
-        var top = new Toplevel ();
-        top.Add (view);
-        Application.Begin (top);
-        ((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
-
-        var expected = @"
-┌──────────────────┐
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-│012345678910111213│
-└──────────────────┘
-";
-
-        Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-        Assert.Equal (new (0, 0, 20, 10), pos);
-
-        view.FillRect (view.Viewport);
-
-        expected = @"
-┌──────────────────┐
-│                  │
-│                  │
-│                  │
-│                  │
-│                  │
-│                  │
-│                  │
-│                  │
-└──────────────────┘
-";
-
-        pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-
-        top.Dispose ();
-    }
-
-    [Theory]
-    [AutoInitShutdown (configLocation: ConfigLocations.Default)]
-    [InlineData (true)]
-    [InlineData (false)]
-    public void Clear_Does_Not_Spillover_Its_Parent (bool label)
-    {
-        var root = new View { Width = 20, Height = 10, ColorScheme = Colors.ColorSchemes ["Base"] };
-
-        string text = new ('c', 100);
-
-        View v = label
-                     // Label has Width/Height == AutoSize, so Frame.Size will be (100, 1)
-                     ? new Label { Text = text }
-                     // TextView has Width/Height == (Dim.Fill, 1), so Frame.Size will be 20 (width of root), 1
-                     : new TextView { Width = Dim.Fill (), Height = 1, Text = text };
-
-        root.Add (v);
-
-        var top = new Toplevel ();
-        top.Add (root);
-        RunState runState = Application.Begin (top);
-        Application.RunIteration (ref runState);
-
-        if (label)
-        {
-            Assert.False (v.CanFocus);
-            Assert.Equal (new (0, 0, text.Length, 1), v.Frame);
-        }
-        else
-        {
-            Assert.True (v.CanFocus);
-            Assert.Equal (new (0, 0, 20, 1), v.Frame);
-        }
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-cccccccccccccccccccc",
-                                                      output
-                                                     );
-
-        Attribute [] attributes =
-        {
-            Colors.ColorSchemes ["TopLevel"].Normal,
-            Colors.ColorSchemes ["Base"].Normal,
-            Colors.ColorSchemes ["Base"].Focus
-        };
-
-        if (label)
-        {
-            TestHelpers.AssertDriverAttributesAre (
-                                                   @"
-111111111111111111110
-111111111111111111110",
-                                                   output,
-                                                   Application.Driver,
-                                                   attributes
-                                                  );
-        }
-        else
-        {
-            TestHelpers.AssertDriverAttributesAre (
-                                                   @"
-222222222222222222220
-111111111111111111110",
-                                                   output,
-                                                   Application.Driver,
-                                                   attributes
-                                                  );
-        }
-
-        if (label)
-        {
-            root.CanFocus = true;
-            v.CanFocus = true;
-            Assert.True (v.HasFocus);
-            v.SetFocus ();
-            Assert.True (v.HasFocus);
-            Application.LayoutAndDraw ();
-
-            TestHelpers.AssertDriverAttributesAre (
-                                                   @"
-222222222222222222220
-111111111111111111110",
-                                                   output,
-                                                   Application.Driver,
-                                                   attributes
-                                                  );
-        }
-
-        Application.End (runState);
-        top.Dispose ();
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Frame ()
-    {
-        var label = new Label { Text = "At 0,0" };
-
-        var view = new DerivedView
-        {
-            X = 2,
-            Y = 2,
-            Width = 30,
-            Height = 2,
-            Text = "A text with some long width\n and also with two lines."
-        };
-        Toplevel top = new ();
-        top.Add (label, view);
-        RunState runState = Application.Begin (top);
-        Application.RunIteration (ref runState);
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
-                             
-  A text with some long width
-   and also with two lines.  ",
-                                                      output
-                                                     );
-
-        view.Frame = new (3, 3, 10, 1);
-        Assert.Equal (new (3, 3, 10, 1), view.Frame);
-        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
-        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
-        //Application.Refresh();
-        top.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0       
-             
-             
-   A text wit",
-                                                      output
-                                                     );
-        Application.End (runState);
-        top.Dispose ();
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Pos_Dim ()
-    {
-        var label = new Label { Text = "At 0,0" };
-
-        var view = new DerivedView
-        {
-            X = 2,
-            Y = 2,
-            Width = 30,
-            Height = 2,
-            Text = "A text with some long width\n and also with two lines."
-        };
-        Toplevel top = new ();
-        top.Add (label, view);
-        RunState runState = Application.Begin (top);
-
-        top.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
-                             
-  A text with some long width
-   and also with two lines.  ",
-                                                      output
-                                                     );
-
-        view.X = 3;
-        view.Y = 3;
-        view.Width = 10;
-        view.Height = 1;
-        Assert.Equal (new (3, 3, 10, 1), view.Frame);
-        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
-        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
-        View.SetClipToScreen ();
-        top.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0       
-             
-             
-   A text wit",
-                                                      output
-                                                     );
-        Application.End (runState);
-        top.Dispose ();
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Frame ()
-    {
-        var label = new Label { Text = "At 0,0" };
-
-        var view = new DerivedView
-        {
-            X = 2,
-            Y = 2,
-            Width = 30,
-            Height = 2,
-            Text = "A text with some long width\n and also with two lines."
-        };
-        Toplevel top = new ();
-        top.Add (label, view);
-        RunState runState = Application.Begin (top);
-        Application.RunIteration (ref runState);
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
-                             
-  A text with some long width
-   and also with two lines.  ",
-                                                      output
-                                                     );
-
-        view.Frame = new (1, 1, 10, 1);
-        Assert.Equal (new (1, 1, 10, 1), view.Frame);
-        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
-        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
-        top.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0     
- A text wit",
-                                                      output
-                                                     );
-        Application.End (runState);
-        top.Dispose ();
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Pos_Dim ()
-    {
-        var label = new Label { Text = "At 0,0" };
-
-        var view = new DerivedView
-        {
-            X = 2,
-            Y = 2,
-            Width = 30,
-            Height = 2,
-            Text = "A text with some long width\n and also with two lines."
-        };
-        Toplevel top = new ();
-        top.Add (label, view);
-        RunState runState = Application.Begin (top);
-
-        top.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
-                             
-  A text with some long width
-   and also with two lines.  ",
-                                                      output
-                                                     );
-
-        view.X = 1;
-        view.Y = 1;
-        view.Width = 10;
-        view.Height = 1;
-        Assert.Equal (new (1, 1, 10, 1), view.Frame);
-        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
-        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
-        View.SetClipToScreen ();
-
-        top.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0     
- A text wit",
-                                                      output
-                                                     );
-        Application.End (runState);
-        top.Dispose ();
-    }
-
     [Fact]
     [TestRespondersDisposed]
     public void Dispose_View ()
@@ -585,301 +173,7 @@ At 0,0
         Assert.Null (view.Border);
         Assert.Null (view.Padding);
     }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void DrawContentComplete_Event_Is_Always_Called ()
-    {
-        var viewCalled = false;
-        var tvCalled = false;
-
-        var view = new View { Width = 10, Height = 10, Text = "View" };
-        view.DrawComplete += (s, e) => viewCalled = true;
-        var tv = new TextView { Y = 11, Width = 10, Height = 10 };
-        tv.DrawComplete += (s, e) => tvCalled = true;
-
-        var top = new Toplevel ();
-        top.Add (view, tv);
-        RunState runState = Application.Begin (top);
-        Application.RunIteration (ref runState);
-
-        Assert.True (viewCalled);
-        Assert.True (tvCalled);
-        top.Dispose ();
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Frame_Set_After_Initialize_Update_NeededDisplay ()
-    {
-        var frame = new FrameView ();
-
-        var label = new Label
-        {
-            ColorScheme = Colors.ColorSchemes ["Menu"], X = 0, Y = 0, Text = "This should be the first line."
-        };
-
-        var view = new View
-        {
-            X = 0, // don't overcomplicate unit tests
-            Y = 1,
-            Height = Dim.Auto (DimAutoStyle.Text),
-            Width = Dim.Auto (DimAutoStyle.Text),
-            Text = "Press me!"
-        };
-
-        frame.Add (label, view);
-
-        frame.X = Pos.Center ();
-        frame.Y = Pos.Center ();
-        frame.Width = 40;
-        frame.Height = 8;
-
-        Toplevel top = new ();
-
-        top.Add (frame);
-
-        RunState runState = Application.Begin (top);
-
-        top.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 80, 25), top._needsDrawRect); };
-
-        frame.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 40, 8), frame._needsDrawRect); };
-
-        label.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 38, 1), label._needsDrawRect); };
-
-        view.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 13, 1), view._needsDrawRect); };
-
-        Assert.Equal (new (0, 0, 80, 25), top.Frame);
-        Assert.Equal (new (20, 8, 40, 8), frame.Frame);
-
-        Assert.Equal (
-                      new (20, 8, 60, 16),
-                      new Rectangle (
-                                     frame.Frame.Left,
-                                     frame.Frame.Top,
-                                     frame.Frame.Right,
-                                     frame.Frame.Bottom
-                                    )
-                     );
-        Assert.Equal (new (0, 0, 30, 1), label.Frame);
-        Assert.Equal (new (0, 1, 9, 1), view.Frame); // this proves frame was set
-        Application.End (runState);
-        top.Dispose ();
-    }
-
-    [Fact]
-    public void GetHotNormalColor_ColorScheme ()
-    {
-        var view = new View { ColorScheme = Colors.ColorSchemes ["Base"] };
-
-        Assert.Equal (view.ColorScheme.HotNormal, view.GetHotNormalColor ());
-
-        view.Enabled = false;
-        Assert.Equal (view.ColorScheme.Disabled, view.GetHotNormalColor ());
-        view.Dispose ();
-    }
-
-    [Fact]
-    public void GetNormalColor_ColorScheme ()
-    {
-        var view = new View { ColorScheme = Colors.ColorSchemes ["Base"] };
-
-        Assert.Equal (view.ColorScheme.Normal, view.GetNormalColor ());
-
-        view.Enabled = false;
-        Assert.Equal (view.ColorScheme.Disabled, view.GetNormalColor ());
-        view.Dispose ();
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Incorrect_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Frame ()
-    {
-        var label = new Label { Text = "At 0,0" };
-
-        var view = new DerivedView
-        {
-            X = 2,
-            Y = 2,
-            Width = 30,
-            Height = 2,
-            Text = "A text with some long width\n and also with two lines."
-        };
-        Toplevel top = new ();
-        top.Add (label, view);
-        RunState runState = Application.Begin (top);
-        Application.RunIteration (ref runState);
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
-                             
-  A text with some long width
-   and also with two lines.  ",
-                                                      output
-                                                     );
-
-        view.Frame = new (3, 3, 10, 1);
-        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
-        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
-        view.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
-                             
-  A text with some long width
-   A text witith two lines.  ",
-                                                      output
-                                                     );
-        Application.End (runState);
-        top.Dispose ();
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Incorrect_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Pos_Dim ()
-    {
-        var label = new Label { Text = "At 0,0" };
-
-        var view = new DerivedView
-        {
-            X = 2,
-            Y = 2,
-            Width = 30,
-            Height = 2,
-            Text = "A text with some long width\n and also with two lines."
-        };
-        Toplevel top = new ();
-        top.Add (label, view);
-        RunState runState = Application.Begin (top);
-        Application.RunIteration (ref runState);
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
-                             
-  A text with some long width
-   and also with two lines.  ",
-                                                      output
-                                                     );
-
-        view.X = 3;
-        view.Y = 3;
-        view.Width = 10;
-        view.Height = 1;
-        Assert.Equal (new (3, 3, 10, 1), view.Frame);
-        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
-        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
-        view.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
-                             
-  A text with some long width
-   A text witith two lines.  ",
-                                                      output
-                                                     );
-        Application.End (runState);
-        top.Dispose ();
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Incorrect_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Frame ()
-    {
-        var label = new Label { Text = "At 0,0" };
-
-        var view = new DerivedView
-        {
-            X = 2,
-            Y = 2,
-            Width = 30,
-            Height = 2,
-            Text = "A text with some long width\n and also with two lines."
-        };
-        Toplevel top = new ();
-        top.Add (label, view);
-        RunState runState = Application.Begin (top);
-        Application.RunIteration (ref runState);
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
-                             
-  A text with some long width
-   and also with two lines.  ",
-                                                      output
-                                                     );
-
-        view.Frame = new (1, 1, 10, 1);
-        Assert.Equal (new (1, 1, 10, 1), view.Frame);
-        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
-        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
-        view.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
- A text wit                  
-  A text with some long width
-   and also with two lines.  ",
-                                                      output
-                                                     );
-        Application.End (runState);
-        top.Dispose ();
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Incorrect_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Pos_Dim ()
-    {
-        var label = new Label { Text = "At 0,0" };
-
-        var view = new DerivedView
-        {
-            X = 2,
-            Y = 2,
-            Width = 30,
-            Height = 2,
-            Text = "A text with some long width\n and also with two lines."
-        };
-        Toplevel top = new ();
-        top.Add (label, view);
-        RunState runState = Application.Begin (top);
-        Application.RunIteration (ref runState);
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
-                             
-  A text with some long width
-   and also with two lines.  ",
-                                                      output
-                                                     );
-
-        view.X = 1;
-        view.Y = 1;
-        view.Width = 10;
-        view.Height = 1;
-        Assert.Equal (new (1, 1, 10, 1), view.Frame);
-        Assert.Equal (new (0, 0, 10, 1), view.Viewport);
-        Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
-        view.Draw ();
-
-        TestHelpers.AssertDriverContentsWithFrameAre (
-                                                      @"
-At 0,0                       
- A text wit                  
-  A text with some long width
-   and also with two lines.  ",
-                                                      output
-                                                     );
-        Application.End (runState);
-        top.Dispose ();
-    }
-
+    
     [Fact]
     public void Internal_Tests ()
     {
@@ -887,20 +181,7 @@ At 0,0
         var view = new View { Frame = rect };
     }
 
-    [Fact]
-    [SetupFakeDriver]
-    public void SetText_RendersCorrectly ()
-    {
-        View view;
-        var text = "test";
-
-        view = new Label { Text = text };
-        view.BeginInit ();
-        view.EndInit ();
-        view.Draw ();
 
-        TestHelpers.AssertDriverContentsWithFrameAre (text, output);
-    }
 
     [Fact]
     [TestRespondersDisposed]
@@ -1243,67 +524,4 @@ At 0,0
         top.Dispose ();
         Assert.Equal (1, iterations);
     }
-
-    public class DerivedView : View
-    {
-        public DerivedView () { CanFocus = true; }
-        public bool IsKeyDown { get; set; }
-        public bool IsKeyPress { get; set; }
-        public bool IsKeyUp { get; set; }
-        public override string Text { get; set; }
-
-        protected override bool OnDrawingContent ()
-        {
-            var idx = 0;
-
-            // BUGBUG: v2 - this should use Viewport, not Frame
-            for (var r = 0; r < Frame.Height; r++)
-            {
-                for (var c = 0; c < Frame.Width; c++)
-                {
-                    if (idx < Text.Length)
-                    {
-                        char rune = Text [idx];
-
-                        if (rune != '\n')
-                        {
-                            AddRune (c, r, (Rune)Text [idx]);
-                        }
-
-                        idx++;
-
-                        if (rune == '\n')
-                        {
-                            break;
-                        }
-                    }
-                }
-            }
-
-            ClearNeedsDraw ();
-
-            return true;
-        }
-
-        protected override bool OnKeyDown (Key keyEvent)
-        {
-            IsKeyDown = true;
-
-            return true;
-        }
-
-        public override bool OnKeyUp (Key keyEvent)
-        {
-            IsKeyUp = true;
-
-            return true;
-        }
-
-        protected override bool OnKeyDownNotHandled (Key keyEvent)
-        {
-            IsKeyPress = true;
-
-            return true;
-        }
-    }
 }

BIN
local_packages/Terminal.Gui.2.0.0.nupkg


BIN
local_packages/Terminal.Gui.2.0.0.snupkg