ScrollSlider.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #nullable enable
  2. namespace Terminal.Gui;
  3. internal class ScrollSlider : View
  4. {
  5. public ScrollSlider (Scroll host)
  6. {
  7. _host = host;
  8. Id = "scrollSlider";
  9. Width = Dim.Auto (DimAutoStyle.Content);
  10. Height = Dim.Auto (DimAutoStyle.Content);
  11. WantMousePositionReports = true;
  12. }
  13. internal bool _wasSliderMouse;
  14. private readonly Scroll _host;
  15. private int _lastLocation = -1;
  16. private ColorScheme? _savedColorScheme;
  17. public void AdjustSlider ()
  18. {
  19. if (!IsInitialized)
  20. {
  21. return;
  22. }
  23. (int Location, int Dimension) sliderLocationAndDimension = GetSliderLocationDimensionFromPosition ();
  24. X = _host.Orientation == Orientation.Vertical ? 0 : sliderLocationAndDimension.Location;
  25. Y = _host.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Location : 0;
  26. SetContentSize (
  27. new (
  28. _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Width : sliderLocationAndDimension.Dimension,
  29. _host.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Dimension : _host.GetContentSize ().Height
  30. ));
  31. SetSliderText ();
  32. }
  33. /// <inheritdoc/>
  34. public override Attribute GetNormalColor ()
  35. {
  36. if (_savedColorScheme is null)
  37. {
  38. ColorScheme = new () { Normal = new (_host.ColorScheme.HotNormal.Foreground, _host.ColorScheme.HotNormal.Foreground) };
  39. }
  40. else
  41. {
  42. ColorScheme = new () { Normal = new (_host.ColorScheme.Normal.Foreground, _host.ColorScheme.Normal.Foreground) };
  43. }
  44. return base.GetNormalColor ();
  45. }
  46. /// <inheritdoc/>
  47. protected internal override bool? OnMouseEnter (MouseEvent mouseEvent)
  48. {
  49. _savedColorScheme ??= _host.ColorScheme;
  50. ColorScheme = new ()
  51. {
  52. Normal = new (_savedColorScheme.HotNormal.Foreground, _savedColorScheme.HotNormal.Foreground),
  53. Focus = new (_savedColorScheme.Focus.Foreground, _savedColorScheme.Focus.Foreground),
  54. HotNormal = new (_savedColorScheme.Normal.Foreground, _savedColorScheme.Normal.Foreground),
  55. HotFocus = new (_savedColorScheme.HotFocus.Foreground, _savedColorScheme.HotFocus.Foreground),
  56. Disabled = new (_savedColorScheme.Disabled.Foreground, _savedColorScheme.Disabled.Foreground)
  57. };
  58. return base.OnMouseEnter (mouseEvent);
  59. }
  60. /// <inheritdoc/>
  61. protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
  62. {
  63. int location = _host.Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X;
  64. int offset = _lastLocation > -1 ? location - _lastLocation : 0;
  65. int barSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width;
  66. if (mouseEvent.Flags == MouseFlags.Button1Pressed)
  67. {
  68. if (Application.MouseGrabView != this)
  69. {
  70. Application.GrabMouse (this);
  71. _lastLocation = location;
  72. }
  73. }
  74. else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
  75. {
  76. _wasSliderMouse = true;
  77. if (_host.Orientation == Orientation.Vertical)
  78. {
  79. Y = Frame.Y + offset < 0 ? 0 :
  80. Frame.Y + offset + Frame.Height > barSize ? Math.Max (barSize - Frame.Height, 0) : Frame.Y + offset;
  81. _host.Position = GetPositionFromSliderLocation (Frame.Y);
  82. }
  83. else
  84. {
  85. X = Frame.X + offset < 0 ? 0 :
  86. Frame.X + offset + Frame.Width > barSize ? Math.Max (barSize - Frame.Width, 0) : Frame.X + offset;
  87. _host.Position = GetPositionFromSliderLocation (Frame.X);
  88. }
  89. }
  90. else if (mouseEvent.Flags == MouseFlags.Button1Released)
  91. {
  92. _lastLocation = -1;
  93. if (Application.MouseGrabView == this)
  94. {
  95. Application.UngrabMouse ();
  96. }
  97. }
  98. else if ((mouseEvent.Flags == MouseFlags.WheeledDown && _host.Orientation == Orientation.Vertical)
  99. || (mouseEvent.Flags == MouseFlags.WheeledRight && _host.Orientation == Orientation.Horizontal))
  100. {
  101. _host.Position = Math.Min (_host.Position + 1, _host.Size - barSize);
  102. }
  103. else if ((mouseEvent.Flags == MouseFlags.WheeledUp && _host.Orientation == Orientation.Vertical)
  104. || (mouseEvent.Flags == MouseFlags.WheeledLeft && _host.Orientation == Orientation.Horizontal))
  105. {
  106. _host.Position = Math.Max (_host.Position - 1, 0);
  107. }
  108. else if (mouseEvent.Flags != MouseFlags.ReportMousePosition)
  109. {
  110. return base.OnMouseEvent (mouseEvent);
  111. }
  112. return true;
  113. }
  114. /// <inheritdoc/>
  115. protected internal override bool OnMouseLeave (MouseEvent mouseEvent)
  116. {
  117. if (_savedColorScheme is { } && !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
  118. {
  119. ColorScheme = _savedColorScheme;
  120. _savedColorScheme = null;
  121. }
  122. return base.OnMouseLeave (mouseEvent);
  123. }
  124. private int GetPositionFromSliderLocation (int location)
  125. {
  126. if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0)
  127. {
  128. return 0;
  129. }
  130. int scrollSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width;
  131. // Ensure the Position is valid if the slider is at end
  132. // We use Frame here instead of ContentSize because even if the slider has a margin or border, Frame indicates the actual size
  133. if ((_host.Orientation == Orientation.Vertical && location + Frame.Height == scrollSize)
  134. || (_host.Orientation == Orientation.Horizontal && location + Frame.Width == scrollSize))
  135. {
  136. return _host.Size - scrollSize;
  137. }
  138. return Math.Min ((location * _host.Size + location) / scrollSize, _host.Size - scrollSize);
  139. }
  140. // QUESTION: This method is only called from one place. Should it be inlined? Or, should it be made internal and unit tests be provided?
  141. private (int Location, int Dimension) GetSliderLocationDimensionFromPosition ()
  142. {
  143. if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0)
  144. {
  145. return new (0, 0);
  146. }
  147. int scrollSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width;
  148. int location;
  149. int dimension;
  150. if (_host.Size > 0)
  151. {
  152. dimension = (int)Math.Min (Math.Max (Math.Ceiling ((double)scrollSize * scrollSize / _host.Size), 1), scrollSize);
  153. // Ensure the Position is valid
  154. if (_host.Position > 0 && _host.Position + scrollSize > _host.Size)
  155. {
  156. _host.Position = _host.Size - scrollSize;
  157. }
  158. location = Math.Min ((_host.Position * scrollSize + _host.Position) / _host.Size, scrollSize - dimension);
  159. if (_host.Position == _host.Size - scrollSize && location + dimension < scrollSize)
  160. {
  161. location = scrollSize - dimension;
  162. }
  163. }
  164. else
  165. {
  166. location = 0;
  167. dimension = scrollSize;
  168. }
  169. return new (location, dimension);
  170. }
  171. // TODO: I think you should create a new `internal` view named "ScrollSlider" with an `Orientation` property. It should inherit from View and override GetNormalColor and the mouse events
  172. // that can be moved within it's Superview, constrained to move only horizontally or vertically depending on Orientation.
  173. // This will really simplify a lot of this.
  174. private void SetSliderText ()
  175. {
  176. TextDirection = _host.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.ContinuousMeterSegment.ToString (),
  181. _host.GetContentSize ().Width * _host.GetContentSize ().Height));
  182. }
  183. }