ScrollSlider.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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. if (_host.Orientation == Orientation.Vertical)
  77. {
  78. if (Frame.Y + offset >= 0 && Frame.Y + offset + Frame.Height <= barSize)
  79. {
  80. _wasSliderMouse = true;
  81. Y = Frame.Y + offset;
  82. _host.Position = GetPositionFromSliderLocation (Frame.Y);
  83. }
  84. }
  85. else
  86. {
  87. if (Frame.X + offset >= 0 && Frame.X + offset + Frame.Width <= barSize)
  88. {
  89. _wasSliderMouse = true;
  90. X = Frame.X + offset;
  91. _host.Position = GetPositionFromSliderLocation (Frame.X);
  92. }
  93. }
  94. }
  95. else if (mouseEvent.Flags == MouseFlags.Button1Released)
  96. {
  97. _lastLocation = -1;
  98. if (Application.MouseGrabView == this)
  99. {
  100. Application.UngrabMouse ();
  101. }
  102. }
  103. else if ((mouseEvent.Flags == MouseFlags.WheeledDown && _host.Orientation == Orientation.Vertical)
  104. || (mouseEvent.Flags == MouseFlags.WheeledRight && _host.Orientation == Orientation.Horizontal))
  105. {
  106. _host.Position = Math.Min (_host.Position + 1, _host.Size - barSize);
  107. }
  108. else if ((mouseEvent.Flags == MouseFlags.WheeledUp && _host.Orientation == Orientation.Vertical)
  109. || (mouseEvent.Flags == MouseFlags.WheeledLeft && _host.Orientation == Orientation.Horizontal))
  110. {
  111. _host.Position = Math.Max (_host.Position - 1, 0);
  112. }
  113. else if (mouseEvent.Flags != MouseFlags.ReportMousePosition)
  114. {
  115. return base.OnMouseEvent (mouseEvent);
  116. }
  117. return true;
  118. }
  119. /// <inheritdoc/>
  120. protected internal override bool OnMouseLeave (MouseEvent mouseEvent)
  121. {
  122. if (_savedColorScheme is { } && !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
  123. {
  124. ColorScheme = _savedColorScheme;
  125. _savedColorScheme = null;
  126. }
  127. return base.OnMouseLeave (mouseEvent);
  128. }
  129. private int GetPositionFromSliderLocation (int location)
  130. {
  131. if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0)
  132. {
  133. return 0;
  134. }
  135. int scrollSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width;
  136. // Ensure the Position is valid if the slider is at end
  137. // We use Frame here instead of ContentSize because even if the slider has a margin or border, Frame indicates the actual size
  138. if ((_host.Orientation == Orientation.Vertical && location + Frame.Height == scrollSize)
  139. || (_host.Orientation == Orientation.Horizontal && location + Frame.Width == scrollSize))
  140. {
  141. return _host.Size - scrollSize;
  142. }
  143. return Math.Min ((location * _host.Size + location) / scrollSize, _host.Size - scrollSize);
  144. }
  145. // QUESTION: This method is only called from one place. Should it be inlined? Or, should it be made internal and unit tests be provided?
  146. private (int Location, int Dimension) GetSliderLocationDimensionFromPosition ()
  147. {
  148. if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0)
  149. {
  150. return new (0, 0);
  151. }
  152. int scrollSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width;
  153. int location;
  154. int dimension;
  155. if (_host.Size > 0)
  156. {
  157. dimension = Math.Min (Math.Max (scrollSize * scrollSize / _host.Size, 1), scrollSize);
  158. // Ensure the Position is valid
  159. if (_host.Position > 0 && _host.Position + scrollSize > _host.Size)
  160. {
  161. _host.Position = _host.Size - scrollSize;
  162. }
  163. location = Math.Min ((_host.Position * scrollSize + _host.Position) / _host.Size, scrollSize - dimension);
  164. if (_host.Position == _host.Size - scrollSize && location + dimension < scrollSize)
  165. {
  166. location = scrollSize - dimension;
  167. }
  168. }
  169. else
  170. {
  171. location = 0;
  172. dimension = scrollSize;
  173. }
  174. return new (location, dimension);
  175. }
  176. // 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
  177. // that can be moved within it's Superview, constrained to move only horizontally or vertically depending on Orientation.
  178. // This will really simplify a lot of this.
  179. private void SetSliderText ()
  180. {
  181. TextDirection = _host.Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
  182. // QUESTION: Should these Glyphs be configurable via CM?
  183. Text = string.Concat (
  184. Enumerable.Repeat (
  185. Glyphs.ContinuousMeterSegment.ToString (),
  186. _host.GetContentSize ().Width * _host.GetContentSize ().Height));
  187. }
  188. }