Scroll.cs 6.6 KB

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