Files
Terminal.Gui/Terminal.Gui/Views/Line.cs
2025-10-03 13:07:49 -07:00

241 lines
8.4 KiB
C#

#nullable enable
namespace Terminal.Gui.Views;
/// <summary>
/// Draws a single line using the <see cref="LineStyle"/> specified by <see cref="Line.Style"/>.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="Line"/> is a <see cref="View"/> that renders a single horizontal or vertical line
/// using the <see cref="LineCanvas"/> system. <see cref="Line"/> integrates with the LineCanvas
/// to enable proper box-drawing character selection and line intersection handling.
/// </para>
/// <para>
/// The line's appearance is controlled by the <see cref="Style"/> property, which supports
/// various line styles including Single, Double, Heavy, Rounded, Dashed, and Dotted.
/// </para>
/// <para>
/// Use the <see cref="Length"/> property to control the extent of the line regardless of its
/// <see cref="Orientation"/>. For horizontal lines, Length controls Width; for vertical lines,
/// it controls Height. The perpendicular dimension is always 1.
/// </para>
/// <para>
/// When multiple <see cref="Line"/> instances or other LineCanvas-aware views (like <see cref="Border"/>)
/// intersect, the LineCanvas automatically selects the appropriate box-drawing characters for corners,
/// T-junctions, and crosses.
/// </para>
/// <para>
/// <see cref="Line"/> sets <see cref="View.SuperViewRendersLineCanvas"/> to <see langword="true"/>,
/// meaning its parent view is responsible for rendering the line. This allows for proper intersection
/// handling when multiple views contribute lines to the same canvas.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// // Create a horizontal line
/// var hLine = new Line { Y = 5 };
///
/// // Create a vertical line with specific length
/// var vLine = new Line { X = 10, Orientation = Orientation.Vertical, Length = 15 };
///
/// // Create a double-line style horizontal line
/// var doubleLine = new Line { Y = 10, Style = LineStyle.Double };
/// </code>
/// </example>
public class Line : View, IOrientation
{
private readonly OrientationHelper _orientationHelper;
private LineStyle _style = LineStyle.Single;
private Dim _length = Dim.Fill ();
/// <summary>
/// Constructs a new instance of the <see cref="Line"/> class with horizontal orientation.
/// </summary>
/// <remarks>
/// By default, a horizontal line fills the available width and has a height of 1.
/// The line style defaults to <see cref="LineStyle.Single"/>.
/// </remarks>
public Line ()
{
CanFocus = false;
base.SuperViewRendersLineCanvas = true;
_orientationHelper = new (this);
_orientationHelper.Orientation = Orientation.Horizontal;
// Set default dimensions for horizontal orientation
// Set Height first (this will update _length, but we'll override it next)
Height = 1;
// Now set Width and _length to Fill
_length = Dim.Fill ();
Width = _length;
}
/// <summary>
/// Gets or sets the length of the line along its orientation.
/// </summary>
/// <remarks>
/// <para>
/// This is the "source of truth" for the line's primary dimension.
/// For a horizontal line, Length controls Width.
/// For a vertical line, Length controls Height.
/// </para>
/// <para>
/// When Width or Height is set directly, Length is updated to match the primary dimension.
/// When Orientation changes, the appropriate dimension is set to Length and the perpendicular
/// dimension is set to 1.
/// </para>
/// <para>
/// This property provides a cleaner API for controlling the line's extent
/// without needing to know whether to use Width or Height.
/// </para>
/// </remarks>
public Dim Length
{
get => Orientation == Orientation.Horizontal ? Width : Height;
set
{
_length = value;
// Update the appropriate dimension based on current orientation
if (Orientation == Orientation.Horizontal)
{
Width = _length;
}
else
{
Height = _length;
}
}
}
/// <summary>
/// Gets or sets the style of the line. This controls the visual appearance of the line.
/// </summary>
/// <remarks>
/// Supports various line styles including Single, Double, Heavy, Rounded, Dashed, and Dotted.
/// Note: This is separate from <see cref="View.BorderStyle"/> to avoid conflicts with the View's Border.
/// </remarks>
public LineStyle Style
{
get => _style;
set
{
if (_style != value)
{
_style = value;
SetNeedsDraw ();
}
}
}
#region IOrientation members
/// <summary>
/// The direction of the line.
/// </summary>
/// <remarks>
/// <para>
/// When orientation changes, the appropriate dimension is set to <see cref="Length"/>
/// and the perpendicular dimension is set to 1.
/// </para>
/// <para>
/// For object initializers where dimensions are set before orientation:
/// <code>new Line { Height = 9, Orientation = Orientation.Vertical }</code>
/// Setting Height=9 updates Length to 9 (since default orientation is Horizontal and Height is perpendicular).
/// Then when Orientation is set to Vertical, Height is set to Length (9) and Width is set to 1,
/// resulting in the expected Width=1, Height=9.
/// </para>
/// </remarks>
public Orientation Orientation
{
get => _orientationHelper.Orientation;
set => _orientationHelper.Orientation = value;
}
#pragma warning disable CS0067 // The event is never used
/// <inheritdoc/>
public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
/// <inheritdoc/>
public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
#pragma warning restore CS0067 // The event is never used
/// <summary>
/// Called when <see cref="Orientation"/> has changed.
/// </summary>
/// <param name="newOrientation">The new orientation value.</param>
public void OnOrientationChanged (Orientation newOrientation)
{
// Set dimensions based on new orientation:
// - Primary dimension (along orientation) = Length
// - Perpendicular dimension = 1
if (newOrientation == Orientation.Horizontal)
{
Width = _length;
Height = 1;
}
else
{
Height = _length;
Width = 1;
}
}
/// <inheritdoc/>
protected override bool OnWidthChanging (ValueChangingEventArgs<Dim> e)
{
// If horizontal, allow width changes and update _length
_length = e.NewValue;
if (Orientation == Orientation.Horizontal)
{
return base.OnWidthChanging (e);
}
// If vertical, keep width at 1 (don't allow changes to perpendicular dimension)
e.NewValue = 1;
return base.OnWidthChanging (e);
}
/// <inheritdoc/>
protected override bool OnHeightChanging (ValueChangingEventArgs<Dim> e)
{
// If vertical, allow height changes and update _length
_length = e.NewValue;
if (Orientation == Orientation.Vertical)
{
return base.OnHeightChanging (e);
}
e.NewValue = 1;
return base.OnHeightChanging (e);
}
#endregion
/// <inheritdoc/>
/// <remarks>
/// This method adds the line to the LineCanvas for rendering.
/// The actual rendering is performed by the parent view through <see cref="View.RenderLineCanvas"/>.
/// </remarks>
protected override bool OnDrawingContent ()
{
Point pos = ViewportToScreen (Viewport).Location;
int length = Orientation == Orientation.Horizontal ? Frame.Width : Frame.Height;
LineCanvas.AddLine (
pos,
length,
Orientation,
Style,
GetAttributeForRole(VisualRole.Normal)
);
return true;
}
}