ScrollSlider.cs 8.3 KB

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