Scroll.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. #nullable enable
  2. using System.ComponentModel;
  3. namespace Terminal.Gui;
  4. /// <summary>
  5. /// Indicates the position and size of scrollable content. The indicator can be dragged with the mouse. Can be
  6. /// oriented either vertically or horizontally. Used within a <see cref="ScrollBar"/>.
  7. /// </summary>
  8. /// <remarks>
  9. /// <para>
  10. /// By default, this view cannot be focused and does not support keyboard.
  11. /// </para>
  12. /// </remarks>
  13. public class Scroll : View
  14. {
  15. /// <inheritdoc/>
  16. public Scroll ()
  17. {
  18. _slider = new ();
  19. Add (_slider);
  20. WantContinuousButtonPressed = true;
  21. CanFocus = false;
  22. Orientation = Orientation.Vertical;
  23. Width = Dim.Auto (DimAutoStyle.Content, 1);
  24. Height = Dim.Auto (DimAutoStyle.Content, 1);
  25. }
  26. internal readonly ScrollSlider _slider;
  27. private Orientation _orientation;
  28. private int _position;
  29. private int _size;
  30. /// <inheritdoc/>
  31. public override void EndInit ()
  32. {
  33. base.EndInit ();
  34. AdjustScroll ();
  35. }
  36. /// <summary>
  37. /// Gets or sets if the Scroll is oriented vertically or horizontally.
  38. /// </summary>
  39. public Orientation Orientation
  40. {
  41. get => _orientation;
  42. set
  43. {
  44. _orientation = value;
  45. AdjustScroll ();
  46. }
  47. }
  48. /// <summary>
  49. /// Gets or sets the position of the start of the Scroll slider, relative to <see cref="Size"/>.
  50. /// </summary>
  51. public int Position
  52. {
  53. get => _position;
  54. set
  55. {
  56. if (value == _position || value < 0)
  57. {
  58. return;
  59. }
  60. if (SupView is { IsInitialized: false })
  61. {
  62. // Ensures a more exactly calculation
  63. SetRelativeLayout (SupView.Frame.Size);
  64. }
  65. int barSize = BarSize;
  66. if (value + barSize > Size)
  67. {
  68. return;
  69. }
  70. CancelEventArgs<int> args = OnPositionChanging (_position, value);
  71. if (args.Cancel)
  72. {
  73. return;
  74. }
  75. _position = value;
  76. AdjustScroll ();
  77. OnPositionChanged (_position);
  78. }
  79. }
  80. /// <summary>Raised when the <see cref="Position"/> has changed.</summary>
  81. public event EventHandler<EventArgs<int>>? PositionChanged;
  82. /// <summary>
  83. /// Raised when the <see cref="Position"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
  84. /// <see langword="true"/> to prevent the position from being changed.
  85. /// </summary>
  86. public event EventHandler<CancelEventArgs<int>>? PositionChanging;
  87. /// <summary>
  88. /// Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through.
  89. /// </summary>
  90. public int Size
  91. {
  92. get => _size;
  93. set
  94. {
  95. _size = value;
  96. OnSizeChanged (_size);
  97. AdjustScroll ();
  98. }
  99. }
  100. /// <summary>Raised when <see cref="Size"/> has changed.</summary>
  101. public event EventHandler<EventArgs<int>>? SizeChanged;
  102. /// <inheritdoc/>
  103. protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
  104. {
  105. int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X;
  106. int barSize = BarSize;
  107. (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical
  108. ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1)
  109. : new (_slider.Frame.X, _slider.Frame.Right - 1);
  110. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location < sliderPos.topLeft)
  111. {
  112. Position = Math.Max (Position - barSize, 0);
  113. }
  114. else if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location > sliderPos.bottomRight)
  115. {
  116. Position = Math.Min (Position + barSize, Size - barSize);
  117. }
  118. else if ((mouseEvent.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical)
  119. || (mouseEvent.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal))
  120. {
  121. Position = Math.Min (Position + 1, Size - barSize);
  122. }
  123. else if ((mouseEvent.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical)
  124. || (mouseEvent.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal))
  125. {
  126. Position = Math.Max (Position - 1, 0);
  127. }
  128. else if (mouseEvent.Flags == MouseFlags.Button1Clicked)
  129. {
  130. if (_slider.Frame.Contains (mouseEvent.Position))
  131. {
  132. return _slider.OnMouseEvent (mouseEvent);
  133. }
  134. }
  135. return base.OnMouseEvent (mouseEvent);
  136. }
  137. // TODO: Move this into "ScrollSlider" and override it there. Scroll can then subscribe to _slider.LayoutComplete and call AdjustSlider.
  138. // QUESTION: I've been meaning to add a "View.FrameChanged" event (fired from LayoutComplete only if Frame has changed). Should we do that as part of this PR?
  139. // QUESTION: Note I *did* add "View.ViewportChanged" in a previous PR.
  140. /// <summary>Virtual method called when <see cref="Position"/> has changed. Raises <see cref="PositionChanged"/>.</summary>
  141. protected virtual void OnPositionChanged (int position) { PositionChanged?.Invoke (this, new (in position)); }
  142. /// <summary>
  143. /// Virtual method called when <see cref="Position"/> is changing. Raises <see cref="PositionChanging"/>, which is
  144. /// cancelable.
  145. /// </summary>
  146. protected virtual CancelEventArgs<int> OnPositionChanging (int currentPos, int newPos)
  147. {
  148. CancelEventArgs<int> args = new (ref currentPos, ref newPos);
  149. PositionChanging?.Invoke (this, args);
  150. return args;
  151. }
  152. /// <summary>Called when <see cref="Size"/> has changed. Raises <see cref="SizeChanged"/>.</summary>
  153. protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (in size)); }
  154. internal void AdjustScroll ()
  155. {
  156. if (SupView is { })
  157. {
  158. X = Orientation == Orientation.Vertical ? 0 : 1;
  159. Y = Orientation == Orientation.Vertical ? 1 : 0;
  160. Width = Orientation == Orientation.Vertical ? Dim.Fill () : Dim.Fill (1);
  161. Height = Orientation == Orientation.Vertical ? Dim.Fill (1) : Dim.Fill ();
  162. }
  163. _slider.AdjustSlider ();
  164. SetScrollText ();
  165. }
  166. /// <inheritdoc/>
  167. internal override void OnLayoutComplete (LayoutEventArgs args)
  168. {
  169. base.OnLayoutComplete (args);
  170. AdjustScroll ();
  171. }
  172. internal ScrollBar? SupView => SuperView as ScrollBar;
  173. private int BarSize => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
  174. private void SetScrollText ()
  175. {
  176. TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
  177. // QUESTION: Should these Glyphs be configurable via CM?
  178. Text = string.Concat (
  179. Enumerable.Repeat (
  180. Glyphs.Stipple.ToString (),
  181. GetContentSize ().Width * GetContentSize ().Height));
  182. }
  183. }