Scroll.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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. private bool _keepContentInAllViewport = true;
  31. /// <inheritdoc/>
  32. public override void EndInit ()
  33. {
  34. base.EndInit ();
  35. AdjustScroll ();
  36. }
  37. /// <summary>Get or sets if the view-port is kept in all visible area of this <see cref="Scroll"/></summary>
  38. public bool KeepContentInAllViewport
  39. {
  40. get => _keepContentInAllViewport;
  41. set
  42. {
  43. if (_keepContentInAllViewport != value)
  44. {
  45. _keepContentInAllViewport = value;
  46. var pos = 0;
  47. if (value && Orientation == Orientation.Horizontal && _position + SuperViewAsScrollBar!.Viewport.Width > Size)
  48. {
  49. pos = Size - SuperViewAsScrollBar.Viewport.Width;
  50. }
  51. if (value && Orientation == Orientation.Vertical && _position + SuperViewAsScrollBar!.Viewport.Height > Size)
  52. {
  53. pos = _size - SuperViewAsScrollBar.Viewport.Height;
  54. }
  55. if (pos != 0)
  56. {
  57. Position = pos;
  58. SetNeedsDisplay ();
  59. AdjustScroll ();
  60. }
  61. }
  62. }
  63. }
  64. /// <summary>
  65. /// Gets or sets if the Scroll is oriented vertically or horizontally.
  66. /// </summary>
  67. public Orientation Orientation
  68. {
  69. get => _orientation;
  70. set
  71. {
  72. _orientation = value;
  73. AdjustScroll ();
  74. }
  75. }
  76. /// <summary>
  77. /// Gets or sets the position of the start of the Scroll slider, relative to <see cref="Size"/>.
  78. /// </summary>
  79. public int Position
  80. {
  81. get => _position;
  82. set
  83. {
  84. if (value == _position || value < 0)
  85. {
  86. return;
  87. }
  88. if (SuperViewAsScrollBar is { IsInitialized: false })
  89. {
  90. // Ensures a more exactly calculation
  91. SetRelativeLayout (SuperViewAsScrollBar.Frame.Size);
  92. }
  93. int pos = SetPosition (value);
  94. if (pos == _position)
  95. {
  96. return;
  97. }
  98. CancelEventArgs<int> args = OnPositionChanging (_position, pos);
  99. if (args.Cancel)
  100. {
  101. return;
  102. }
  103. _position = pos;
  104. AdjustScroll ();
  105. OnPositionChanged (_position);
  106. }
  107. }
  108. /// <summary>Raised when the <see cref="Position"/> has changed.</summary>
  109. public event EventHandler<EventArgs<int>>? PositionChanged;
  110. /// <summary>
  111. /// Raised when the <see cref="Position"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
  112. /// <see langword="true"/> to prevent the position from being changed.
  113. /// </summary>
  114. public event EventHandler<CancelEventArgs<int>>? PositionChanging;
  115. /// <summary>
  116. /// Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through.
  117. /// </summary>
  118. public int Size
  119. {
  120. get => _size;
  121. set
  122. {
  123. if (value == _size || value < 0)
  124. {
  125. return;
  126. }
  127. _size = value;
  128. OnSizeChanged (_size);
  129. AdjustScroll ();
  130. }
  131. }
  132. /// <summary>Raised when <see cref="Size"/> has changed.</summary>
  133. public event EventHandler<EventArgs<int>>? SizeChanged;
  134. /// <inheritdoc/>
  135. protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
  136. {
  137. int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X;
  138. int barSize = BarSize;
  139. (int start, int end) sliderPos = _orientation == Orientation.Vertical
  140. ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1)
  141. : new (_slider.Frame.X, _slider.Frame.Right - 1);
  142. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location < sliderPos.start)
  143. {
  144. Position = Math.Max (Position - barSize, 0);
  145. }
  146. else if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location > sliderPos.end)
  147. {
  148. Position = Math.Min (Position + barSize, Size - barSize);
  149. }
  150. else if ((mouseEvent.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical)
  151. || (mouseEvent.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal))
  152. {
  153. Position = Math.Min (Position + 1, Size - barSize);
  154. }
  155. else if ((mouseEvent.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical)
  156. || (mouseEvent.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal))
  157. {
  158. Position = Math.Max (Position - 1, 0);
  159. }
  160. else if (mouseEvent.Flags == MouseFlags.Button1Clicked)
  161. {
  162. if (_slider.Frame.Contains (mouseEvent.Position))
  163. {
  164. return _slider.OnMouseEvent (mouseEvent);
  165. }
  166. }
  167. return base.OnMouseEvent (mouseEvent);
  168. }
  169. /// <summary>Virtual method called when <see cref="Position"/> has changed. Raises <see cref="PositionChanged"/>.</summary>
  170. protected virtual void OnPositionChanged (int position) { PositionChanged?.Invoke (this, new (in position)); }
  171. /// <summary>
  172. /// Virtual method called when <see cref="Position"/> is changing. Raises <see cref="PositionChanging"/>, which is
  173. /// cancelable.
  174. /// </summary>
  175. protected virtual CancelEventArgs<int> OnPositionChanging (int currentPos, int newPos)
  176. {
  177. CancelEventArgs<int> args = new (ref currentPos, ref newPos);
  178. PositionChanging?.Invoke (this, args);
  179. return args;
  180. }
  181. /// <summary>Called when <see cref="Size"/> has changed. Raises <see cref="SizeChanged"/>.</summary>
  182. protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (in size)); }
  183. internal void AdjustScroll ()
  184. {
  185. if (SuperViewAsScrollBar is { })
  186. {
  187. X = Orientation == Orientation.Vertical ? 0 : 1;
  188. Y = Orientation == Orientation.Vertical ? 1 : 0;
  189. Width = Orientation == Orientation.Vertical ? Dim.Fill () : Dim.Fill (1);
  190. Height = Orientation == Orientation.Vertical ? Dim.Fill (1) : Dim.Fill ();
  191. }
  192. _slider.AdjustSlider ();
  193. SetScrollText ();
  194. }
  195. /// <inheritdoc/>
  196. internal override void OnLayoutComplete (LayoutEventArgs args)
  197. {
  198. base.OnLayoutComplete (args);
  199. AdjustScroll ();
  200. }
  201. internal ScrollBar? SuperViewAsScrollBar => SuperView as ScrollBar;
  202. private int BarSize => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
  203. private int SetPosition (int position)
  204. {
  205. int barSize = BarSize;
  206. if (position + barSize > Size)
  207. {
  208. return KeepContentInAllViewport ? Math.Max (Size - barSize, 0) : Math.Max (Size - 1, 0);
  209. }
  210. return position;
  211. }
  212. private void SetScrollText ()
  213. {
  214. TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
  215. // QUESTION: Should these Glyphs be configurable via CM?
  216. Text = string.Concat (
  217. Enumerable.Repeat (
  218. Glyphs.Stipple.ToString (),
  219. GetContentSize ().Width * GetContentSize ().Height));
  220. }
  221. }