Scroll.cs 7.2 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 (SuperViewAsScrollBar is { IsInitialized: false })
  61. {
  62. // Ensures a more exactly calculation
  63. SetRelativeLayout (SuperViewAsScrollBar.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 start, int end) 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.start)
  111. {
  112. int distance = sliderPos.start - location;
  113. int scrollAmount = (int)((double)distance / barSize * (Size - barSize));
  114. Position = Math.Max (Position - scrollAmount, 0);
  115. }
  116. else if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location > sliderPos.end)
  117. {
  118. int distance = location - sliderPos.end;
  119. int scrollAmount = (int)((double)distance / barSize * (Size - barSize));
  120. Position = Math.Min (Position + scrollAmount, Size - barSize);
  121. }
  122. else if ((mouseEvent.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical)
  123. || (mouseEvent.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal))
  124. {
  125. Position = Math.Min (Position + 1, Size - barSize);
  126. }
  127. else if ((mouseEvent.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical)
  128. || (mouseEvent.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal))
  129. {
  130. Position = Math.Max (Position - 1, 0);
  131. }
  132. else if (mouseEvent.Flags == MouseFlags.Button1Clicked)
  133. {
  134. if (_slider.Frame.Contains (mouseEvent.Position))
  135. {
  136. return _slider.OnMouseEvent (mouseEvent);
  137. }
  138. }
  139. return base.OnMouseEvent (mouseEvent);
  140. }
  141. /// <summary>Virtual method called when <see cref="Position"/> has changed. Raises <see cref="PositionChanged"/>.</summary>
  142. protected virtual void OnPositionChanged (int position) { PositionChanged?.Invoke (this, new (in position)); }
  143. /// <summary>
  144. /// Virtual method called when <see cref="Position"/> is changing. Raises <see cref="PositionChanging"/>, which is
  145. /// cancelable.
  146. /// </summary>
  147. protected virtual CancelEventArgs<int> OnPositionChanging (int currentPos, int newPos)
  148. {
  149. CancelEventArgs<int> args = new (ref currentPos, ref newPos);
  150. PositionChanging?.Invoke (this, args);
  151. return args;
  152. }
  153. /// <summary>Called when <see cref="Size"/> has changed. Raises <see cref="SizeChanged"/>.</summary>
  154. protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (in size)); }
  155. internal void AdjustScroll ()
  156. {
  157. if (SuperViewAsScrollBar is { })
  158. {
  159. X = Orientation == Orientation.Vertical ? 0 : 1;
  160. Y = Orientation == Orientation.Vertical ? 1 : 0;
  161. Width = Orientation == Orientation.Vertical ? Dim.Fill () : Dim.Fill (1);
  162. Height = Orientation == Orientation.Vertical ? Dim.Fill (1) : Dim.Fill ();
  163. }
  164. _slider.AdjustSlider ();
  165. SetScrollText ();
  166. }
  167. /// <inheritdoc/>
  168. internal override void OnLayoutComplete (LayoutEventArgs args)
  169. {
  170. base.OnLayoutComplete (args);
  171. AdjustScroll ();
  172. }
  173. internal ScrollBar? SuperViewAsScrollBar => SuperView as ScrollBar;
  174. private int BarSize => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
  175. private void SetScrollText ()
  176. {
  177. TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
  178. // QUESTION: Should these Glyphs be configurable via CM?
  179. Text = string.Concat (
  180. Enumerable.Repeat (
  181. Glyphs.Stipple.ToString (),
  182. GetContentSize ().Width * GetContentSize ().Height));
  183. }
  184. }