Line.cs 8.4 KB

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