namespace Terminal.Gui;
#nullable enable
// TODO: Add events that notify when StraightLine changes to enable dynamic layout
/// A line between two points on a horizontal or vertical and a given style/color.
public class StraightLine
{
/// Creates a new instance of the class.
///
///
///
///
///
public StraightLine (
Point start,
int length,
Orientation orientation,
LineStyle style,
Attribute? attribute = default
)
{
Start = start;
Length = length;
Orientation = orientation;
Style = style;
Attribute = attribute;
}
/// Gets or sets the color of the line.
public Attribute? Attribute { get; set; }
/// Gets or sets the length of the line.
public int Length { get; set; }
/// Gets or sets the orientation (horizontal or vertical) of the line.
public Orientation Orientation { get; set; }
/// Gets or sets where the line begins.
public Point Start { get; set; }
/// Gets or sets the line style of the line (e.g. dotted, double).
public LineStyle Style { get; set; }
///
/// Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the line that is
/// furthest left/top and Size is defined by the line that extends the furthest right/bottom.
///
// PERF: Probably better to store the rectangle rather than make a new one on every single access to Viewport.
internal Rectangle Viewport
{
get
{
// 0 and 1/-1 Length means a size (width or height) of 1
int size = Math.Max (1, Math.Abs (Length));
// How much to offset x or y to get the start of the line
int offset = Math.Abs (Length < 0 ? Length + 1 : 0);
int x = Start.X - (Orientation == Orientation.Horizontal ? offset : 0);
int y = Start.Y - (Orientation == Orientation.Vertical ? offset : 0);
int width = Orientation == Orientation.Horizontal ? size : 1;
int height = Orientation == Orientation.Vertical ? size : 1;
return new (x, y, width, height);
}
}
/// Formats the Line as a string in (Start.X,Start.Y,Length,Orientation) notation.
public override string ToString () { return $"({Start.X},{Start.Y},{Length},{Orientation})"; }
internal IntersectionDefinition? Intersects (int x, int y)
{
switch (Orientation)
{
case Orientation.Horizontal: return IntersectsHorizontally (x, y);
case Orientation.Vertical: return IntersectsVertically (x, y);
default: throw new ArgumentOutOfRangeException (nameof (Orientation));
}
}
private bool EndsAt (int x, int y)
{
int sub = Length == 0 ? 0 :
Length > 0 ? 1 : -1;
if (Orientation == Orientation.Horizontal)
{
return Start.X + Length - sub == x && Start.Y == y;
}
return Start.X == x && Start.Y + Length - sub == y;
}
private IntersectionType GetTypeByLength (
IntersectionType typeWhenNegative,
IntersectionType typeWhenZero,
IntersectionType typeWhenPositive
)
{
if (Length == 0)
{
return typeWhenZero;
}
return Length < 0 ? typeWhenNegative : typeWhenPositive;
}
private IntersectionDefinition? IntersectsHorizontally (int x, int y)
{
if (Start.Y != y)
{
return null;
}
var p = new Point (x, y);
if (StartsAt (x, y))
{
return new (
p,
GetTypeByLength (
IntersectionType.StartLeft,
IntersectionType.PassOverHorizontal,
IntersectionType.StartRight
),
this
);
}
if (EndsAt (x, y))
{
return new (
p,
Length < 0 ? IntersectionType.StartRight : IntersectionType.StartLeft,
this
);
}
int xmin = Math.Min (Start.X, Start.X + Length);
int xmax = Math.Max (Start.X, Start.X + Length);
if (xmin < x && xmax > x)
{
return new (
p,
IntersectionType.PassOverHorizontal,
this
);
}
return null;
}
private IntersectionDefinition? IntersectsVertically (int x, int y)
{
if (Start.X != x)
{
return null;
}
var p = new Point (x, y);
if (StartsAt (x, y))
{
return new (
p,
GetTypeByLength (
IntersectionType.StartUp,
IntersectionType.PassOverVertical,
IntersectionType.StartDown
),
this
);
}
if (EndsAt (x, y))
{
return new (
p,
Length < 0 ? IntersectionType.StartDown : IntersectionType.StartUp,
this
);
}
int ymin = Math.Min (Start.Y, Start.Y + Length);
int ymax = Math.Max (Start.Y, Start.Y + Length);
if (ymin < y && ymax > y)
{
return new (
p,
IntersectionType.PassOverVertical,
this
);
}
return null;
}
private bool StartsAt (int x, int y) { return Start.X == x && Start.Y == y; }
}