Line.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. #nullable enable
  2. namespace Terminal.Gui.Views;
  3. /// <summary>
  4. /// Draws a single line using the <see cref="LineStyle"/> specified by <see cref="Line.Style"/>.
  5. /// </summary>
  6. /// <remarks>
  7. /// <para>
  8. /// <see cref="Line"/> is a <see cref="View"/> that renders a single horizontal or vertical line
  9. /// using the <see cref="LineCanvas"/> system. Unlike <see cref="LineView"/>, which directly renders
  10. /// runes, <see cref="Line"/> integrates with the LineCanvas to enable proper box-drawing character
  11. /// selection and line intersection handling.
  12. /// </para>
  13. /// <para>
  14. /// The line's appearance is controlled by the <see cref="Style"/> property, which supports
  15. /// various line styles including Single, Double, Heavy, Rounded, Dashed, and Dotted.
  16. /// </para>
  17. /// <para>
  18. /// Use the <see cref="Length"/> property to control the extent of the line regardless of its
  19. /// <see cref="Orientation"/>. For horizontal lines, Length controls Width; for vertical lines,
  20. /// it controls Height. The perpendicular dimension is always 1.
  21. /// </para>
  22. /// <para>
  23. /// When multiple <see cref="Line"/> instances or other LineCanvas-aware views (like <see cref="Border"/>)
  24. /// intersect, the LineCanvas automatically selects the appropriate box-drawing characters for corners,
  25. /// T-junctions, and crosses.
  26. /// </para>
  27. /// <para>
  28. /// <see cref="Line"/> sets <see cref="View.SuperViewRendersLineCanvas"/> to <see langword="true"/>,
  29. /// meaning its parent view is responsible for rendering the line. This allows for proper intersection
  30. /// handling when multiple views contribute lines to the same canvas.
  31. /// </para>
  32. /// </remarks>
  33. /// <example>
  34. /// <code>
  35. /// // Create a horizontal line
  36. /// var hLine = new Line { Y = 5 };
  37. ///
  38. /// // Create a vertical line with specific length
  39. /// var vLine = new Line { X = 10, Orientation = Orientation.Vertical, Length = 15 };
  40. ///
  41. /// // Create a double-line style horizontal line
  42. /// var doubleLine = new Line { Y = 10, Style = LineStyle.Double };
  43. /// </code>
  44. /// </example>
  45. public class Line : View, IOrientation
  46. {
  47. private readonly OrientationHelper _orientationHelper;
  48. private LineStyle _style = LineStyle.Single;
  49. private Dim _length = Dim.Fill ();
  50. /// <summary>
  51. /// Constructs a new instance of the <see cref="Line"/> class with horizontal orientation.
  52. /// </summary>
  53. /// <remarks>
  54. /// By default, a horizontal line fills the available width and has a height of 1.
  55. /// The line style defaults to <see cref="LineStyle.Single"/>.
  56. /// </remarks>
  57. public Line ()
  58. {
  59. CanFocus = false;
  60. base.SuperViewRendersLineCanvas = true;
  61. _orientationHelper = new (this);
  62. _orientationHelper.Orientation = Orientation.Horizontal;
  63. // Set default dimensions for horizontal orientation
  64. // Set Height first (this will update _length, but we'll override it next)
  65. Height = 1;
  66. // Now set Width and _length to Fill
  67. _length = Dim.Fill ();
  68. Width = _length;
  69. }
  70. /// <summary>
  71. /// Gets or sets the length of the line along its orientation.
  72. /// </summary>
  73. /// <remarks>
  74. /// <para>
  75. /// This is the "source of truth" for the line's primary dimension.
  76. /// For a horizontal line, Length controls Width.
  77. /// For a vertical line, Length controls Height.
  78. /// </para>
  79. /// <para>
  80. /// When Width or Height is set directly, Length is updated to match the primary dimension.
  81. /// When Orientation changes, the appropriate dimension is set to Length and the perpendicular
  82. /// dimension is set to 1.
  83. /// </para>
  84. /// <para>
  85. /// This property provides a cleaner API for controlling the line's extent
  86. /// without needing to know whether to use Width or Height.
  87. /// </para>
  88. /// </remarks>
  89. public Dim Length
  90. {
  91. get => Orientation == Orientation.Horizontal ? Width : Height;
  92. set
  93. {
  94. _length = value;
  95. // Update the appropriate dimension based on current orientation
  96. if (Orientation == Orientation.Horizontal)
  97. {
  98. Width = _length;
  99. }
  100. else
  101. {
  102. Height = _length;
  103. }
  104. }
  105. }
  106. /// <summary>
  107. /// Gets or sets the style of the line. This controls the visual appearance of the line.
  108. /// </summary>
  109. /// <remarks>
  110. /// Supports various line styles including Single, Double, Heavy, Rounded, Dashed, and Dotted.
  111. /// Note: This is separate from <see cref="View.BorderStyle"/> to avoid conflicts with the View's Border.
  112. /// </remarks>
  113. public LineStyle Style
  114. {
  115. get => _style;
  116. set
  117. {
  118. if (_style != value)
  119. {
  120. _style = value;
  121. SetNeedsDraw ();
  122. }
  123. }
  124. }
  125. #region IOrientation members
  126. /// <summary>
  127. /// The direction of the line.
  128. /// </summary>
  129. /// <remarks>
  130. /// <para>
  131. /// When orientation changes, the appropriate dimension is set to <see cref="Length"/>
  132. /// and the perpendicular dimension is set to 1.
  133. /// </para>
  134. /// <para>
  135. /// For object initializers where dimensions are set before orientation:
  136. /// <code>new Line { Height = 9, Orientation = Orientation.Vertical }</code>
  137. /// Setting Height=9 updates Length to 9 (since default orientation is Horizontal and Height is perpendicular).
  138. /// Then when Orientation is set to Vertical, Height is set to Length (9) and Width is set to 1,
  139. /// resulting in the expected Width=1, Height=9.
  140. /// </para>
  141. /// </remarks>
  142. public Orientation Orientation
  143. {
  144. get => _orientationHelper.Orientation;
  145. set => _orientationHelper.Orientation = value;
  146. }
  147. #pragma warning disable CS0067 // The event is never used
  148. /// <inheritdoc/>
  149. public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
  150. /// <inheritdoc/>
  151. public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
  152. #pragma warning restore CS0067 // The event is never used
  153. /// <summary>
  154. /// Called when <see cref="Orientation"/> has changed.
  155. /// </summary>
  156. /// <param name="newOrientation">The new orientation value.</param>
  157. public void OnOrientationChanged (Orientation newOrientation)
  158. {
  159. // Set dimensions based on new orientation:
  160. // - Primary dimension (along orientation) = Length
  161. // - Perpendicular dimension = 1
  162. if (newOrientation == Orientation.Horizontal)
  163. {
  164. Width = _length;
  165. Height = 1;
  166. }
  167. else
  168. {
  169. Height = _length;
  170. Width = 1;
  171. }
  172. }
  173. /// <inheritdoc/>
  174. protected override bool OnWidthChanging (ValueChangingEventArgs<Dim> e)
  175. {
  176. // If horizontal, allow width changes and update _length
  177. _length = e.NewValue;
  178. if (Orientation == Orientation.Horizontal)
  179. {
  180. return base.OnWidthChanging (e);
  181. }
  182. // If vertical, keep width at 1 (don't allow changes to perpendicular dimension)
  183. e.NewValue = 1;
  184. return base.OnWidthChanging (e);
  185. }
  186. /// <inheritdoc/>
  187. protected override bool OnHeightChanging (ValueChangingEventArgs<Dim> e)
  188. {
  189. // If vertical, allow height changes and update _length
  190. _length = e.NewValue;
  191. if (Orientation == Orientation.Vertical)
  192. {
  193. return base.OnHeightChanging (e);
  194. }
  195. e.NewValue = 1;
  196. return base.OnHeightChanging (e);
  197. }
  198. #endregion
  199. /// <inheritdoc/>
  200. /// <remarks>
  201. /// This method adds the line to the LineCanvas for rendering.
  202. /// The actual rendering is performed by the parent view through <see cref="View.RenderLineCanvas"/>.
  203. /// </remarks>
  204. protected override bool OnDrawingContent ()
  205. {
  206. Point pos = ViewportToScreen (Viewport).Location;
  207. int length = Orientation == Orientation.Horizontal ? Frame.Width : Frame.Height;
  208. LineCanvas.AddLine (
  209. pos,
  210. length,
  211. Orientation,
  212. Style
  213. );
  214. return true;
  215. }
  216. }