diff --git a/Terminal.Gui/Drawing/Region.cs b/Terminal.Gui/Drawing/Region.cs
new file mode 100644
index 000000000..a1f6c889f
--- /dev/null
+++ b/Terminal.Gui/Drawing/Region.cs
@@ -0,0 +1,269 @@
+///
+/// Represents a region composed of one or more rectangles, providing methods for union, intersection, exclusion, and
+/// complement operations.
+///
+public class Region : IDisposable
+{
+ private List _rectangles;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Region () { _rectangles = new (); }
+
+ ///
+ /// Initializes a new instance of the class with the specified rectangle.
+ ///
+ /// The initial rectangle for the region.
+ public Region (Rectangle rectangle) { _rectangles = new() { rectangle }; }
+
+ ///
+ /// Adds the specified rectangle to the region.
+ ///
+ /// The rectangle to add to the region.
+ public void Union (Rectangle rectangle)
+ {
+ _rectangles.Add (rectangle);
+ _rectangles = MergeRectangles (_rectangles);
+ }
+
+ ///
+ /// Adds the specified region to this region.
+ ///
+ /// The region to add to this region.
+ public void Union (Region region)
+ {
+ _rectangles.AddRange (region._rectangles);
+ _rectangles = MergeRectangles (_rectangles);
+ }
+
+ ///
+ /// Updates the region to be the intersection of itself with the specified rectangle.
+ ///
+ /// The rectangle to intersect with the region.
+ public void Intersect (Rectangle rectangle)
+ {
+ _rectangles = _rectangles.Select (r => Rectangle.Intersect (r, rectangle)).Where (r => !r.IsEmpty).ToList ();
+ }
+
+ ///
+ /// Updates the region to be the intersection of itself with the specified region.
+ ///
+ /// The region to intersect with this region.
+ public void Intersect (Region region)
+ {
+ List intersections = new List ();
+
+ foreach (Rectangle rect1 in _rectangles)
+ {
+ foreach (Rectangle rect2 in region._rectangles)
+ {
+ Rectangle intersected = Rectangle.Intersect (rect1, rect2);
+
+ if (!intersected.IsEmpty)
+ {
+ intersections.Add (intersected);
+ }
+ }
+ }
+
+ _rectangles = intersections;
+ }
+
+ ///
+ /// Removes the portion of the specified rectangle from the region.
+ ///
+ /// The rectangle to exclude from the region.
+ public void Exclude (Rectangle rectangle) { _rectangles = _rectangles.SelectMany (r => SubtractRectangle (r, rectangle)).ToList (); }
+
+ ///
+ /// Removes the portion of the specified region from this region.
+ ///
+ /// The region to exclude from this region.
+ public void Exclude (Region region)
+ {
+ foreach (Rectangle rect in region._rectangles)
+ {
+ _rectangles = _rectangles.SelectMany (r => SubtractRectangle (r, rect)).ToList ();
+ }
+ }
+
+ ///
+ /// Updates the region to be the complement of itself within the specified bounds.
+ ///
+ /// The bounding rectangle to use for complementing the region.
+ public void Complement (Rectangle bounds)
+ {
+ if (bounds.IsEmpty || _rectangles.Count == 0)
+ {
+ _rectangles.Clear ();
+
+ return;
+ }
+
+ List complementRectangles = new List { bounds };
+
+ foreach (Rectangle rect in _rectangles)
+ {
+ complementRectangles = complementRectangles.SelectMany (r => SubtractRectangle (r, rect)).ToList ();
+ }
+
+ _rectangles = complementRectangles;
+ }
+
+ ///
+ /// Creates an exact copy of the region.
+ ///
+ /// A new that is a copy of this instance.
+ public Region Clone ()
+ {
+ var clone = new Region ();
+ clone._rectangles = new (_rectangles);
+
+ return clone;
+ }
+
+ ///
+ /// Gets a bounding rectangle for the entire region.
+ ///
+ /// A that bounds the region.
+ public Rectangle GetBounds ()
+ {
+ if (_rectangles.Count == 0)
+ {
+ 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);
+
+ return new (left, top, right - left, bottom - top);
+ }
+
+ ///
+ /// Determines whether the region is empty.
+ ///
+ /// true if the region is empty; otherwise, false.
+ public bool IsEmpty () { return !_rectangles.Any (); }
+
+ ///
+ /// Determines whether the specified point is contained within the region.
+ ///
+ /// The x-coordinate of the point.
+ /// The y-coordinate of the point.
+ /// true if the point is contained within the region; otherwise, false.
+ public bool Contains (int x, int y) { return _rectangles.Any (r => r.Contains (x, y)); }
+
+ ///
+ /// Determines whether the specified rectangle is contained within the region.
+ ///
+ /// The rectangle to check for containment.
+ /// true if the rectangle is contained within the region; otherwise, false.
+ public bool Contains (Rectangle rectangle) { return _rectangles.Any (r => r.Contains (rectangle)); }
+
+ ///
+ /// Returns an array of rectangles that represent the region.
+ ///
+ /// An array of objects that make up the region.
+ public Rectangle [] GetRegionScans () { return _rectangles.ToArray (); }
+
+ ///
+ /// Merges overlapping rectangles into a minimal set of non-overlapping rectangles.
+ ///
+ /// The list of rectangles to merge.
+ /// A list of merged rectangles.
+ private List MergeRectangles (List rectangles)
+ {
+ // 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 merged = new List (rectangles);
+ bool mergedAny;
+
+ do
+ {
+ mergedAny = false;
+
+ for (var i = 0; i < merged.Count; i++)
+ {
+ for (int j = i + 1; j < merged.Count; j++)
+ {
+ if (merged [i].IntersectsWith (merged [j]))
+ {
+ merged [i] = Rectangle.Union (merged [i], merged [j]);
+ merged.RemoveAt (j);
+ mergedAny = true;
+
+ break;
+ }
+ }
+
+ if (mergedAny)
+ {
+ break;
+ }
+ }
+ }
+ while (mergedAny);
+
+ return merged;
+ }
+
+ ///
+ /// Subtracts the specified rectangle from the original rectangle, returning the resulting rectangles.
+ ///
+ /// The original rectangle.
+ /// The rectangle to subtract from the original.
+ /// An enumerable collection of resulting rectangles after subtraction.
+ private IEnumerable SubtractRectangle (Rectangle original, Rectangle subtract)
+ {
+ if (!original.IntersectsWith (subtract))
+ {
+ yield return original;
+
+ yield break;
+ }
+
+ // Top segment
+ if (original.Top < subtract.Top)
+ {
+ yield return new (original.Left, original.Top, original.Width, subtract.Top - original.Top);
+ }
+
+ // Bottom segment
+ if (original.Bottom > subtract.Bottom)
+ {
+ yield return new (original.Left, subtract.Bottom, original.Width, original.Bottom - subtract.Bottom);
+ }
+
+ // Left segment
+ if (original.Left < subtract.Left)
+ {
+ int top = Math.Max (original.Top, subtract.Top);
+ int bottom = Math.Min (original.Bottom, subtract.Bottom);
+
+ if (bottom > top)
+ {
+ yield return new (original.Left, top, subtract.Left - original.Left, bottom - top);
+ }
+ }
+
+ // Right segment
+ if (original.Right > subtract.Right)
+ {
+ int top = Math.Max (original.Top, subtract.Top);
+ int bottom = Math.Min (original.Bottom, subtract.Bottom);
+
+ if (bottom > top)
+ {
+ yield return new (subtract.Right, top, original.Right - subtract.Right, bottom - top);
+ }
+ }
+ }
+
+ ///
+ /// Releases all resources used by the .
+ ///
+ public void Dispose () { _rectangles.Clear (); }
+}
diff --git a/Terminal.Gui/View/View.Drawing.cs b/Terminal.Gui/View/View.Drawing.cs
index e9b7232e2..c94e39695 100644
--- a/Terminal.Gui/View/View.Drawing.cs
+++ b/Terminal.Gui/View/View.Drawing.cs
@@ -1,7 +1,7 @@
#nullable enable
+#define HACK_DRAW_OVERLAPPED
using System.ComponentModel;
using System.Diagnostics;
-using Microsoft.CodeAnalysis.Operations;
namespace Terminal.Gui;
@@ -430,23 +430,25 @@ public partial class View // Drawing APIs
return;
}
+#if HACK_DRAW_OVERLAPPED
IEnumerable subviewsNeedingDraw = _subviews.Where (view => (view.Visible && (view.NeedsDraw || view.SubViewNeedsDraw))
- );//|| view.Arrangement.HasFlag (ViewArrangement.Overlapped));
+ || view.Arrangement.HasFlag (ViewArrangement.Overlapped));
+#else
+ IEnumerable subviewsNeedingDraw = _subviews.Where (view => (view.Visible && (view.NeedsDraw || view.SubViewNeedsDraw)));
+#endif
foreach (View view in subviewsNeedingDraw)
{
+#if HACK_DRAW_OVERLAPPED
if (view.Arrangement.HasFlag (ViewArrangement.Overlapped))
{
- //view.SetNeedsDraw ();
- }
- view.Draw ();
- }
- //var overlapped = subviewsNeedingDraw;
- //foreach (View view in overlapped)
- //{
- // view.Draw ();
- //}
+ view.SetNeedsDraw ();
+ }
+#endif
+ view.Draw ();
+
+ }
}
#endregion DrawSubviews
diff --git a/UnitTests/Drawing/RegionTests.cs b/UnitTests/Drawing/RegionTests.cs
new file mode 100644
index 000000000..4015f167d
--- /dev/null
+++ b/UnitTests/Drawing/RegionTests.cs
@@ -0,0 +1,2147 @@
+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