mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-30 17:57:57 +01:00
Added a port of System.Drawing.Region with unit tests.
This commit is contained in:
269
Terminal.Gui/Drawing/Region.cs
Normal file
269
Terminal.Gui/Drawing/Region.cs
Normal file
@@ -0,0 +1,269 @@
|
||||
/// <summary>
|
||||
/// Represents a region composed of one or more rectangles, providing methods for union, intersection, exclusion, and
|
||||
/// complement operations.
|
||||
/// </summary>
|
||||
public class Region : IDisposable
|
||||
{
|
||||
private List<Rectangle> _rectangles;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Region"/> class.
|
||||
/// </summary>
|
||||
public Region () { _rectangles = new (); }
|
||||
|
||||
/// <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 }; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified rectangle to the region.
|
||||
/// </summary>
|
||||
/// <param name="rectangle">The rectangle to add to the region.</param>
|
||||
public void Union (Rectangle rectangle)
|
||||
{
|
||||
_rectangles.Add (rectangle);
|
||||
_rectangles = MergeRectangles (_rectangles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified region to this region.
|
||||
/// </summary>
|
||||
/// <param name="region">The region to add to this region.</param>
|
||||
public void Union (Region region)
|
||||
{
|
||||
_rectangles.AddRange (region._rectangles);
|
||||
_rectangles = MergeRectangles (_rectangles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the region to be the intersection of itself with the specified rectangle.
|
||||
/// </summary>
|
||||
/// <param name="rectangle">The rectangle to intersect with the region.</param>
|
||||
public void Intersect (Rectangle rectangle)
|
||||
{
|
||||
_rectangles = _rectangles.Select (r => Rectangle.Intersect (r, rectangle)).Where (r => !r.IsEmpty).ToList ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the region to be the intersection of itself with the specified region.
|
||||
/// </summary>
|
||||
/// <param name="region">The region to intersect with this region.</param>
|
||||
public void Intersect (Region region)
|
||||
{
|
||||
List<Rectangle> intersections = new List<Rectangle> ();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the portion of the specified rectangle from 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 (); }
|
||||
|
||||
/// <summary>
|
||||
/// Removes the portion of the specified region from this region.
|
||||
/// </summary>
|
||||
/// <param name="region">The region to exclude from this region.</param>
|
||||
public void Exclude (Region region)
|
||||
{
|
||||
foreach (Rectangle rect in region._rectangles)
|
||||
{
|
||||
_rectangles = _rectangles.SelectMany (r => SubtractRectangle (r, rect)).ToList ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
List<Rectangle> complementRectangles = new List<Rectangle> { bounds };
|
||||
|
||||
foreach (Rectangle rect in _rectangles)
|
||||
{
|
||||
complementRectangles = complementRectangles.SelectMany (r => SubtractRectangle (r, rect)).ToList ();
|
||||
}
|
||||
|
||||
_rectangles = complementRectangles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an exact copy of 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);
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a bounding rectangle for the entire region.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Rectangle"/> that bounds the region.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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 () { return !_rectangles.Any (); }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified point is contained within 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) { return _rectangles.Any (r => r.Contains (x, y)); }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified rectangle is contained within the region.
|
||||
/// </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) { return _rectangles.Any (r => r.Contains (rectangle)); }
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array of rectangles that represent the region.
|
||||
/// </summary>
|
||||
/// <returns>An array of <see cref="Rectangle"/> objects that make up the region.</returns>
|
||||
public Rectangle [] GetRegionScans () { return _rectangles.ToArray (); }
|
||||
|
||||
/// <summary>
|
||||
/// Merges overlapping rectangles into a minimal set of non-overlapping rectangles.
|
||||
/// </summary>
|
||||
/// <param name="rectangles">The list of rectangles to merge.</param>
|
||||
/// <returns>A list of merged rectangles.</returns>
|
||||
private List<Rectangle> MergeRectangles (List<Rectangle> 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<Rectangle> merged = new List<Rectangle> (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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts the specified rectangle from the original rectangle, returning the resulting rectangles.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases all resources used by the <see cref="Region"/>.
|
||||
/// </summary>
|
||||
public void Dispose () { _rectangles.Clear (); }
|
||||
}
|
||||
@@ -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<View> subviewsNeedingDraw = _subviews.Where (view => (view.Visible && (view.NeedsDraw || view.SubViewNeedsDraw))
|
||||
);//|| view.Arrangement.HasFlag (ViewArrangement.Overlapped));
|
||||
|| view.Arrangement.HasFlag (ViewArrangement.Overlapped));
|
||||
#else
|
||||
IEnumerable<View> 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
|
||||
|
||||
2147
UnitTests/Drawing/RegionTests.cs
Normal file
2147
UnitTests/Drawing/RegionTests.cs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user