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