ScrollSlider.cs 11 KB

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