namespace Terminal.Gui; /// Extension methods for (including collections). public static class StraightLineExtensions { /// /// Splits or removes all lines in the such that none cover the given exclusion /// area. /// /// Lines to adjust /// First point to remove from collection /// The number of sequential points to exclude /// Orientation of the exclusion line /// public static IEnumerable Exclude ( this IEnumerable collection, Point start, int length, Orientation orientation ) { List toReturn = new (); if (length == 0) { return collection; } foreach (StraightLine l in collection) { if (l.Length == 0) { toReturn.Add (l); continue; } // lines are parallel. For any straight line one axis (x or y) is constant // e.g. Horizontal lines have constant y int econstPoint = orientation == Orientation.Horizontal ? start.Y : start.X; int lconstPoint = l.Orientation == Orientation.Horizontal ? l.Start.Y : l.Start.X; // For the varying axis what is the max/mins // i.e. points on horizontal lines vary by x, vertical lines vary by y int eDiffMin = GetLineStartOnDiffAxis (start, length, orientation); int eDiffMax = GetLineEndOnDiffAxis (start, length, orientation); int lDiffMin = GetLineStartOnDiffAxis (l.Start, l.Length, l.Orientation); int lDiffMax = GetLineEndOnDiffAxis (l.Start, l.Length, l.Orientation); // line is parallel to exclusion if (l.Orientation == orientation) { // Do the parallel lines share constant plane if (econstPoint != lconstPoint) { // No, so no way they overlap toReturn.Add (l); } else { if (lDiffMax < eDiffMin) { // Line ends before exclusion starts toReturn.Add (l); } else if (lDiffMin > eDiffMax) { // Line starts after exclusion ends toReturn.Add (l); } else { //lines overlap! // Is there a bit we can keep on the left? if (lDiffMin < eDiffMin) { // Create line up to exclusion point int from = lDiffMin; int len = eDiffMin - lDiffMin; if (len > 0) { toReturn.Add (CreateLineFromDiff (l, from, len)); } } // Is there a bit we can keep on the right? if (lDiffMax > eDiffMax) { // Create line up to exclusion point int from = eDiffMax + 1; int len = lDiffMax - eDiffMax; if (len > 0) { // A single line with length 1 and -1 are the same (fills only the single cell) // They differ only in how they join to other lines (i.e. to create corners) // Using negative for the later half of the line ensures line joins in a way // consistent with its pre-snipped state. if (len == 1) { len = -1; } toReturn.Add (CreateLineFromDiff (l, from, len)); } } } } } else { // line is perpendicular to exclusion // Does the constant plane of the exclusion appear within the differing plane of the line? if (econstPoint >= lDiffMin && econstPoint <= lDiffMax) { // Yes, e.g. Vertical exclusion's x is within xmin/xmax of the horizontal line // Vice versa must also be true // for example there is no intersection if the vertical exclusion line does not // stretch down far enough to reach the line if (lconstPoint >= eDiffMin && lconstPoint <= eDiffMax) { // Perpendicular intersection occurs here Point intersection = l.Orientation == Orientation.Horizontal ? new Point (econstPoint, lconstPoint) : new Point (lconstPoint, econstPoint); // To snip out this single point we will use a recursive call // snipping 1 length along the orientation of l (i.e. parallel) toReturn.AddRange (new [] { l }.Exclude (intersection, 1, l.Orientation)); } else { // No intersection toReturn.Add (l); } } else { // Lines do not intersect toReturn.Add (l); } } } return toReturn; } /// /// Creates a new line which is part of from the point on the varying axis /// to . Horizontal lines have points that vary by x while vertical /// lines have points that vary by y /// /// Line to create sub part from /// Point on varying axis to start at /// Length of line to return /// The new line private static StraightLine CreateLineFromDiff (StraightLine l, int from, int length) { var start = new Point ( l.Orientation == Orientation.Horizontal ? from : l.Start.X, l.Orientation == Orientation.Horizontal ? l.Start.Y : from ); return new StraightLine (start, length, l.Orientation, l.Style, l.Attribute); } /// /// /// Calculates the single digit point where a line ends on the differing axis i.e. the maximum (controlling for /// negative lengths). /// /// /// For lines with this is an x coordinate. For lines that are /// this is a y coordinate. /// /// /// Where the line starts /// Length of the line /// Orientation of the line /// The maximum x or y (whichever is differing) point on the line, controlling for negative lengths. private static int GetLineEndOnDiffAxis (Point start, int length, Orientation orientation) { if (length == 0) { throw new ArgumentException ("0 length lines are not supported", nameof (length)); } int sub = length > 0 ? 1 : -1; if (orientation == Orientation.Vertical) { // Points on line differ by y return Math.Max (start.Y + length - sub, start.Y); } // Points on line differ by x return Math.Max (start.X + length - sub, start.X); } /// /// /// Calculates the single digit point where a line starts on the differing axis i.e. the minimum (controlling for /// negative lengths). /// /// /// For lines with this is an x coordinate. For lines that are /// this is a y coordinate. /// /// /// Where the line starts /// Length of the line /// Orientation of the line /// The minimum x or y (whichever is differing) point on the line, controlling for negative lengths. private static int GetLineStartOnDiffAxis (Point start, int length, Orientation orientation) { if (length == 0) { throw new ArgumentException ("0 length lines are not supported", nameof (length)); } int sub = length > 0 ? 1 : -1; if (orientation == Orientation.Vertical) { // Points on line differ by y return Math.Min (start.Y + length - sub, start.Y); } // Points on line differ by x return Math.Min (start.X + length - sub, start.X); } }