ScrollSlider.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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. private readonly Scroll _host;
  14. private int _lastLocation = -1;
  15. private ColorScheme? _savedColorScheme;
  16. public void AdjustSlider ()
  17. {
  18. if (!IsInitialized)
  19. {
  20. return;
  21. }
  22. (int Location, int Dimension) sliderLocationAndDimension = GetSliderLocationDimensionFromPosition ();
  23. X = _host.Orientation == Orientation.Vertical ? 0 : sliderLocationAndDimension.Location;
  24. Y = _host.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Location : 0;
  25. SetContentSize (
  26. new (
  27. _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Width : sliderLocationAndDimension.Dimension,
  28. _host.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Dimension : _host.GetContentSize ().Height
  29. ));
  30. SetSliderText ();
  31. }
  32. /// <inheritdoc/>
  33. public override Attribute GetNormalColor ()
  34. {
  35. if (_savedColorScheme is null)
  36. {
  37. ColorScheme = new () { Normal = new (_host.ColorScheme.HotNormal.Foreground, _host.ColorScheme.HotNormal.Foreground) };
  38. }
  39. else
  40. {
  41. ColorScheme = new () { Normal = new (_host.ColorScheme.Normal.Foreground, _host.ColorScheme.Normal.Foreground) };
  42. }
  43. return base.GetNormalColor ();
  44. }
  45. /// <inheritdoc/>
  46. protected internal override bool? OnMouseEnter (MouseEvent mouseEvent)
  47. {
  48. _savedColorScheme ??= _host.ColorScheme;
  49. ColorScheme = new ()
  50. {
  51. Normal = new (_savedColorScheme.HotNormal.Foreground, _savedColorScheme.HotNormal.Foreground),
  52. Focus = new (_savedColorScheme.Focus.Foreground, _savedColorScheme.Focus.Foreground),
  53. HotNormal = new (_savedColorScheme.Normal.Foreground, _savedColorScheme.Normal.Foreground),
  54. HotFocus = new (_savedColorScheme.HotFocus.Foreground, _savedColorScheme.HotFocus.Foreground),
  55. Disabled = new (_savedColorScheme.Disabled.Foreground, _savedColorScheme.Disabled.Foreground)
  56. };
  57. return base.OnMouseEnter (mouseEvent);
  58. }
  59. /// <inheritdoc/>
  60. protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
  61. {
  62. if (!_host._wasSliderLayoutComplete)
  63. {
  64. // Ensure not blocking scroll mouse event
  65. _host._wasSliderLayoutComplete = true;
  66. }
  67. int location = _host.Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X;
  68. int offset = _lastLocation > -1 ? location - _lastLocation : 0;
  69. int barSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width;
  70. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && _lastLocation == -1)
  71. {
  72. if (Application.MouseGrabView != this)
  73. {
  74. Application.GrabMouse (this);
  75. _lastLocation = location;
  76. }
  77. }
  78. else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
  79. {
  80. if (_host.Orientation == Orientation.Vertical)
  81. {
  82. Y = Frame.Y + offset < 0 ? 0 :
  83. Frame.Y + offset + Frame.Height > barSize ? Math.Max (barSize - Frame.Height, 0) : Frame.Y + offset;
  84. _host.Position = GetPositionFromSliderLocation (Frame.Y);
  85. }
  86. else
  87. {
  88. X = Frame.X + offset < 0 ? 0 :
  89. Frame.X + offset + Frame.Width > barSize ? Math.Max (barSize - Frame.Width, 0) : Frame.X + offset;
  90. _host.Position = GetPositionFromSliderLocation (Frame.X);
  91. }
  92. }
  93. else if (mouseEvent.Flags == MouseFlags.Button1Released)
  94. {
  95. _lastLocation = -1;
  96. if (Application.MouseGrabView == this)
  97. {
  98. Application.UngrabMouse ();
  99. }
  100. }
  101. else if ((mouseEvent.Flags == MouseFlags.WheeledDown && _host.Orientation == Orientation.Vertical)
  102. || (mouseEvent.Flags == MouseFlags.WheeledRight && _host.Orientation == Orientation.Horizontal))
  103. {
  104. _host.Position = Math.Min (_host.Position + 1, _host.Size - barSize);
  105. }
  106. else if ((mouseEvent.Flags == MouseFlags.WheeledUp && _host.Orientation == Orientation.Vertical)
  107. || (mouseEvent.Flags == MouseFlags.WheeledLeft && _host.Orientation == Orientation.Horizontal))
  108. {
  109. _host.Position = Math.Max (_host.Position - 1, 0);
  110. }
  111. else if (mouseEvent.Flags != MouseFlags.ReportMousePosition)
  112. {
  113. return base.OnMouseEvent (mouseEvent);
  114. }
  115. return true;
  116. }
  117. /// <inheritdoc/>
  118. protected internal override bool OnMouseLeave (MouseEvent mouseEvent)
  119. {
  120. if (_savedColorScheme is { } && !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
  121. {
  122. ColorScheme = _savedColorScheme;
  123. _savedColorScheme = null;
  124. }
  125. return base.OnMouseLeave (mouseEvent);
  126. }
  127. internal int GetPositionFromSliderLocation (int location)
  128. {
  129. if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0)
  130. {
  131. return 0;
  132. }
  133. int scrollSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width;
  134. // Ensure the Position is valid if the slider is at end
  135. // We use Frame here instead of ContentSize because even if the slider has a margin or border, Frame indicates the actual size
  136. if ((_host.Orientation == Orientation.Vertical && location + Frame.Height >= scrollSize)
  137. || (_host.Orientation == Orientation.Horizontal && location + Frame.Width >= scrollSize))
  138. {
  139. return _host.Size - scrollSize;
  140. }
  141. return (int)Math.Min (Math.Round ((double)(location * _host.Size + location) / scrollSize), _host.Size - scrollSize);
  142. }
  143. internal (int Location, int Dimension) GetSliderLocationDimensionFromPosition ()
  144. {
  145. if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0)
  146. {
  147. return new (0, 0);
  148. }
  149. int scrollSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width;
  150. int location;
  151. int dimension;
  152. if (_host.Size > 0)
  153. {
  154. dimension = (int)Math.Min (Math.Max (Math.Ceiling ((double)scrollSize * scrollSize / _host.Size), 1), scrollSize);
  155. // Ensure the Position is valid
  156. if (_host.Position > 0 && _host.Position + scrollSize > _host.Size)
  157. {
  158. _host.Position = _host.Size - scrollSize;
  159. }
  160. location = (int)Math.Min (Math.Round ((double)_host.Position * scrollSize / (_host.Size + 1)), scrollSize - dimension);
  161. if (_host.Position == _host.Size - scrollSize && location + dimension < scrollSize)
  162. {
  163. location = scrollSize - dimension;
  164. }
  165. }
  166. else
  167. {
  168. location = 0;
  169. dimension = scrollSize;
  170. }
  171. return new (location, dimension);
  172. }
  173. /// <inheritdoc/>
  174. internal override void OnLayoutComplete (LayoutEventArgs args)
  175. {
  176. base.OnLayoutComplete (args);
  177. _host._wasSliderLayoutComplete = true;
  178. }
  179. // 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
  180. // that can be moved within it's Superview, constrained to move only horizontally or vertically depending on Orientation.
  181. // This will really simplify a lot of this.
  182. private void SetSliderText ()
  183. {
  184. TextDirection = _host.Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
  185. // QUESTION: Should these Glyphs be configurable via CM?
  186. Text = string.Concat (
  187. Enumerable.Repeat (
  188. Glyphs.ContinuousMeterSegment.ToString (),
  189. _host.GetContentSize ().Width * _host.GetContentSize ().Height));
  190. }
  191. }