Browse Source

Fightin math.

Tig 8 months ago
parent
commit
13fca41c23

+ 2 - 2
Terminal.Gui/Drawing/Glyphs.cs

@@ -79,10 +79,10 @@ public class GlyphDefinitions
     /// <summary>Continuous block meter segment (e.g. for <see cref="ProgressBar"/>).</summary>
     public Rune ContinuousMeterSegment { get; set; } = (Rune)'█';
 
-    /// <summary>Stipple pattern (e.g. for <see cref="ScrollBarView"/>). Default is Light Shade (U+2591) - ░.</summary>
+    /// <summary>Stipple pattern (e.g. for <see cref="ScrollBar"/>). Default is Light Shade (U+2591) - ░.</summary>
     public Rune Stipple { get; set; } = (Rune)'░';
 
-    /// <summary>Diamond (e.g. for <see cref="ScrollBarView"/>. Default is Lozenge (U+25CA) - ◊.</summary>
+    /// <summary>Diamond. Default is Lozenge (U+25CA) - ◊.</summary>
     public Rune Diamond { get; set; } = (Rune)'◊';
 
     /// <summary>Close. Default is Heavy Ballot X (U+2718) - ✘.</summary>

+ 118 - 118
Terminal.Gui/View/View.ScrollBars.cs

@@ -11,131 +11,131 @@ public partial class View
     /// </summary>
     private void SetupScrollBars ()
     {
-        _horizontalScrollBar = new (
-                                    () =>
-                                    {
-                                        var scrollBar = new ScrollBar
-                                        {
-                                            Orientation = Orientation.Horizontal,
-                                            X = 0,
-                                            Y = Pos.AnchorEnd (),
-                                            Width = Dim.Fill (
-                                                              Dim.Func (
-                                                                        () =>
-                                                                        {
-                                                                            if (_verticalScrollBar.IsValueCreated)
-                                                                            {
-                                                                                return _verticalScrollBar.Value.Visible ? 1 : 0;
-                                                                            }
-
-                                                                            return 0;
-                                                                        })),
-                                            Size = GetContentSize ().Width,
-                                            Visible = false
-                                        };
-
-                                        Padding?.Add (scrollBar);
-
-                                        scrollBar.Initialized += (_, _) =>
-                                        {
-                                            Padding!.Thickness = Padding.Thickness with
-                                            {
-                                                Bottom = scrollBar.Visible ? Padding.Thickness.Bottom + 1 : 0
-                                            };
-
-                                            scrollBar.ContentPositionChanged += (_, args) =>
-                                                                                {
-                                                                                    Viewport = Viewport with
-                                                                                    {
-                                                                                        X = Math.Min (
-                                                                                                      args.CurrentValue,
-                                                                                                      GetContentSize ().Width - (Viewport.Width))
-                                                                                    };
-                                                                                };
-
-                                            scrollBar.VisibleChanged += (_, _) =>
-                                            {
-                                                Padding.Thickness = Padding.Thickness with
-                                                {
-                                                    Bottom = scrollBar.Visible
-                                                        ? Padding.Thickness.Bottom + 1
-                                                        : Padding.Thickness.Bottom - 1
-                                                };
-                                            };
-                                        };
-
-                                        return scrollBar;
-                                    });
-
-        _verticalScrollBar = new (
-                                  () =>
-                                  {
-                                      var scrollBar = new ScrollBar
-                                      {
-                                          Orientation = Orientation.Vertical,
-                                          X = Pos.AnchorEnd (),
-                                          Y = Pos.Func (() => Padding.Thickness.Top),
-                                          Height = Dim.Fill (
-                                                             Dim.Func (
-                                                                       () =>
-                                                                       {
-                                                                           if (_horizontalScrollBar.IsValueCreated)
-                                                                           {
-                                                                               return _horizontalScrollBar.Value.Visible ? 1 : 0;
-                                                                           }
-
-                                                                           return 0;
-                                                                       })),
-                                          Size = GetContentSize ().Height,
-                                          Visible = false
-                                      };
-
-                                      Padding?.Add (scrollBar);
-
-                                      scrollBar.Initialized += (_, _) =>
-                                      {
-                                          if (Padding is { })
-                                          {
-                                              Padding.Thickness = Padding.Thickness with
-                                              {
-                                                  Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : 0
-                                              };
-
-                                              scrollBar.ContentPositionChanged += (_, args) =>
-                                                                                  {
-                                                                                      Viewport = Viewport with
-                                                                                      {
-                                                                                          Y = Math.Min (args.CurrentValue, GetContentSize ().Height - (Viewport.Height - 1))
-                                                                                      };
-                                                                                  };
-
-                                              scrollBar.VisibleChanged += (_, _) =>
-                                                                          {
-                                                                              Padding.Thickness = Padding.Thickness with
-                                                                              {
-                                                                                  Right = scrollBar.Visible
-                                                                                              ? Padding.Thickness.Right + 1
-                                                                                              : Padding.Thickness.Right - 1
-                                                                              };
-                                                                          };
-                                          }
-                                      };
-
-                                      return scrollBar;
-                                  });
+        _horizontalScrollBar = new Lazy<ScrollBar> (
+                                                    () =>
+                                                    {
+                                                        var scrollBar = new ScrollBar
+                                                        {
+                                                            Orientation = Orientation.Horizontal,
+                                                            X = 0,
+                                                            Y = Pos.AnchorEnd (),
+                                                            Width = Dim.Fill (
+                                                                              Dim.Func (
+                                                                                        () =>
+                                                                                        {
+                                                                                            if (_verticalScrollBar.IsValueCreated)
+                                                                                            {
+                                                                                                return _verticalScrollBar.Value.Visible ? 1 : 0;
+                                                                                            }
+
+                                                                                            return 0;
+                                                                                        })),
+                                                            ScrollableContentSize = GetContentSize ().Width,
+                                                            Visible = false
+                                                        };
+
+                                                        Padding?.Add (scrollBar);
+
+                                                        scrollBar.Initialized += (_, _) =>
+                                                                                 {
+                                                                                     Padding!.Thickness = Padding.Thickness with
+                                                                                     {
+                                                                                         Bottom = scrollBar.Visible ? Padding.Thickness.Bottom + 1 : 0
+                                                                                     };
+
+                                                                                     scrollBar.PositionChanged += (_, args) =>
+                                                                                                                  {
+                                                                                                                      Viewport = Viewport with
+                                                                                                                      {
+                                                                                                                          X = Math.Min (
+                                                                                                                                        args.CurrentValue,
+                                                                                                                                        GetContentSize ().Width - (Viewport.Width))
+                                                                                                                      };
+                                                                                                                  };
+
+                                                                                     scrollBar.VisibleChanged += (_, _) =>
+                                                                                                                 {
+                                                                                                                     Padding.Thickness = Padding.Thickness with
+                                                                                                                     {
+                                                                                                                         Bottom = scrollBar.Visible
+                                                                                                                                      ? Padding.Thickness.Bottom + 1
+                                                                                                                                      : Padding.Thickness.Bottom - 1
+                                                                                                                     };
+                                                                                                                 };
+                                                                                 };
+
+                                                        return scrollBar;
+                                                    });
+
+        _verticalScrollBar = new Lazy<ScrollBar> (
+                                                  () =>
+                                                  {
+                                                      var scrollBar = new ScrollBar
+                                                      {
+                                                          Orientation = Orientation.Vertical,
+                                                          X = Pos.AnchorEnd (),
+                                                          Y = Pos.Func (() => Padding.Thickness.Top),
+                                                          Height = Dim.Fill (
+                                                                             Dim.Func (
+                                                                                       () =>
+                                                                                       {
+                                                                                           if (_horizontalScrollBar.IsValueCreated)
+                                                                                           {
+                                                                                               return _horizontalScrollBar.Value.Visible ? 1 : 0;
+                                                                                           }
+
+                                                                                           return 0;
+                                                                                       })),
+                                                          ScrollableContentSize = GetContentSize ().Height,
+                                                          Visible = false
+                                                      };
+
+                                                      Padding?.Add (scrollBar);
+
+                                                      scrollBar.Initialized += (_, _) =>
+                                                                               {
+                                                                                   if (Padding is { })
+                                                                                   {
+                                                                                       Padding.Thickness = Padding.Thickness with
+                                                                                       {
+                                                                                           Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : 0
+                                                                                       };
+
+                                                                                       scrollBar.PositionChanged += (_, args) =>
+                                                                                                                    {
+                                                                                                                        Viewport = Viewport with
+                                                                                                                        {
+                                                                                                                            Y = Math.Min (args.CurrentValue, GetContentSize ().Height - (Viewport.Height - 1))
+                                                                                                                        };
+                                                                                                                    };
+
+                                                                                       scrollBar.VisibleChanged += (_, _) =>
+                                                                                                                   {
+                                                                                                                       Padding.Thickness = Padding.Thickness with
+                                                                                                                       {
+                                                                                                                           Right = scrollBar.Visible
+                                                                                                                                       ? Padding.Thickness.Right + 1
+                                                                                                                                       : Padding.Thickness.Right - 1
+                                                                                                                       };
+                                                                                                                   };
+                                                                                   }
+                                                                               };
+
+                                                      return scrollBar;
+                                                  });
 
         ViewportChanged += (_, _) =>
         {
             if (_verticalScrollBar.IsValueCreated)
             {
-                _verticalScrollBar.Value.ViewportDimension = Viewport.Height;
-                _verticalScrollBar.Value.ContentPosition = Viewport.Y;
+                _verticalScrollBar.Value.VisibleContentSize = Viewport.Height;
+                _verticalScrollBar.Value.Position = Viewport.Y;
             }
 
             if (_horizontalScrollBar.IsValueCreated)
             {
-                _horizontalScrollBar.Value.ViewportDimension = Viewport.Width;
-                _horizontalScrollBar.Value.ContentPosition = Viewport.X;
+                _horizontalScrollBar.Value.VisibleContentSize = Viewport.Width;
+                _horizontalScrollBar.Value.Position = Viewport.X;
             }
         };
 
@@ -143,11 +143,11 @@ public partial class View
         {
             if (_verticalScrollBar.IsValueCreated)
             {
-                _verticalScrollBar.Value.Size = GetContentSize ().Height;
+                _verticalScrollBar.Value.ScrollableContentSize = GetContentSize ().Height;
             }
             if (_horizontalScrollBar.IsValueCreated)
             {
-                _horizontalScrollBar.Value.Size = GetContentSize ().Width;
+                _horizontalScrollBar.Value.ScrollableContentSize = GetContentSize ().Width;
             }
         };
     }

+ 219 - 188
Terminal.Gui/Views/ScrollBar/ScrollBar.cs

@@ -38,7 +38,7 @@ public class ScrollBar : View, IOrientation, IDesignable
 
         _slider = new ()
         {
-            ShrinkBy = 2, // For the buttons
+            SliderPadding = 2, // For the buttons
         };
         _slider.Scrolled += SliderOnScroll;
         _slider.PositionChanged += SliderOnPositionChanged;
@@ -66,13 +66,13 @@ public class ScrollBar : View, IOrientation, IDesignable
 
         void OnDecreaseButtonOnAccept (object? s, CommandEventArgs e)
         {
-            ContentPosition -= Increment;
+            Position -= Increment;
             e.Cancel = true;
         }
 
         void OnIncreaseButtonOnAccept (object? s, CommandEventArgs e)
         {
-            ContentPosition += Increment;
+            Position += Increment;
             e.Cancel = true;
         }
     }
@@ -80,54 +80,42 @@ public class ScrollBar : View, IOrientation, IDesignable
     /// <inheritdoc/>
     protected override void OnFrameChanged (in Rectangle frame)
     {
+        if (Orientation == Orientation.Vertical)
+        {
+            _slider.VisibleContentSize = Viewport.Height;
+        }
+        else
+        {
+            _slider.VisibleContentSize = Viewport.Width ;
+        }
+
         ShowHide ();
     }
 
     private void ShowHide ()
     {
-        if (!AutoHide || !IsInitialized)
+        if (!AutoHide)
         {
             return;
         }
 
         if (Orientation == Orientation.Vertical)
         {
-            Visible = Frame.Height < Size;
+            Visible = Frame.Height < ScrollableContentSize;
         }
         else
         {
-            Visible = Frame.Width < Size;
+            Visible = Frame.Width < ScrollableContentSize;
         }
-    }
 
-    /// <inheritdoc/>
-    protected override void OnSubviewLayout (LayoutEventArgs args)
-    {
         _slider.Size = CalculateSliderSize ();
-
-        if (Orientation == Orientation.Vertical)
-        {
-            _slider.ViewportDimension = Viewport.Height - _slider.ShrinkBy;
-        }
-        else
-        {
-            _slider.ViewportDimension = Viewport.Width - _slider.ShrinkBy;
-        }
+        _slider.Position = CalculateSliderPosition (_position, NavigationDirection.Forward);
     }
 
-    /// <summary>
-    ///     INTERNAL (for unit tests). Calclates the size of the slider based on the Orientation, ViewportDimension, the actual Viewport, and Size.
-    /// </summary>
-    /// <returns></returns>
-    internal int CalculateSliderSize ()
+    /// <inheritdoc/>
+    protected override void OnSubviewLayout (LayoutEventArgs args)
     {
-        if (Size <= 0 || ViewportDimension <= 0)
-        {
-            return 1;
-        }
 
-        int viewport = Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
-        return (int)Math.Clamp (Math.Floor ((double)ViewportDimension / Size * (viewport - 2)), 1, ViewportDimension);
     }
 
     private void PositionSubviews ()
@@ -215,12 +203,19 @@ public class ScrollBar : View, IOrientation, IDesignable
 
     #endregion
 
-
+    /// <summary>
+    ///     Gets or sets the amount each mouse wheel event will incremenet/decrement the <see cref="Position"/>.
+    /// </summary>
+    /// <remarks>
+    ///     The default is 1.
+    /// </remarks>
+    public int Increment { get; set; } = 1;
+    
     private bool _autoHide = true;
 
     /// <summary>
     ///     Gets or sets whether <see cref="View.Visible"/> will be set to <see langword="false"/> if the dimension of the
-    ///     scroll bar is greater than or equal to <see cref="Size"/>.
+    ///     scroll bar is greater than or equal to <see cref="ScrollableContentSize"/>.
     /// </summary>
     public bool AutoHide
     {
@@ -251,7 +246,7 @@ public class ScrollBar : View, IOrientation, IDesignable
 
     /// <summary>
     ///     Gets or sets whether the Scroll will show the percentage the slider
-    ///     takes up within the <see cref="Size"/>.
+    ///     takes up within the <see cref="ScrollableContentSize"/>.
     /// </summary>
     public bool ShowPercent
     {
@@ -259,236 +254,282 @@ public class ScrollBar : View, IOrientation, IDesignable
         set => _slider.ShowPercent = value;
     }
 
-    private int? _viewportDimension;
+    private int? _visibleContentSize;
 
     /// <summary>
-    ///     Gets or sets the size of the viewport into the content being scrolled, bounded by <see cref="Size"/>.
+    ///     Gets or sets the size of the visible viewport into the content being scrolled, bounded by <see cref="ScrollableContentSize"/>.
     /// </summary>
     /// <remarks>
     ///     If not explicitly set, will be the appropriate dimension of the Scroll's Frame.
     /// </remarks>
-    public int ViewportDimension
+    public int VisibleContentSize
     {
         get
         {
-            if (_viewportDimension.HasValue)
+            if (_visibleContentSize.HasValue)
             {
-                return _viewportDimension.Value;
+                return _visibleContentSize.Value;
             }
             return Orientation == Orientation.Vertical ? Frame.Height : Frame.Width;
 
         }
-        set => _viewportDimension = value;
+        set
+        {
+            _visibleContentSize = value;
+            _slider.Size = CalculateSliderSize ();
+        }
     }
 
-    private int _size;
+    private int _scrollableContentSize;
 
     /// <summary>
-    ///     Gets or sets the size of the content that can be scrolled.
+    ///     Gets or sets the size of the content that can be scrolled. This is typically set to <see cref="View.GetContentSize()"/>.
     /// </summary>
-    public int Size
+    public int ScrollableContentSize
     {
-        get => _size;
+        get => _scrollableContentSize;
         set
         {
-            if (value == _size || value < 0)
+            if (value == _scrollableContentSize || value < 0)
             {
                 return;
             }
 
-            _size = value;
-            OnSizeChanged (_size);
-            SizeChanged?.Invoke (this, new (in _size));
+            _scrollableContentSize = value;
+            _slider.Size = CalculateSliderSize ();
+            OnSizeChanged (_scrollableContentSize);
+            ScrollableContentSizeChanged?.Invoke (this, new (in _scrollableContentSize));
             SetNeedsLayout ();
         }
     }
 
-    /// <summary>Called when <see cref="Size"/> has changed. </summary>
+    /// <summary>Called when <see cref="ScrollableContentSize"/> has changed. </summary>
     protected virtual void OnSizeChanged (int size) { }
 
-    /// <summary>Raised when <see cref="Size"/> has changed.</summary>
-    public event EventHandler<EventArgs<int>>? SizeChanged;
+    /// <summary>Raised when <see cref="ScrollableContentSize"/> has changed.</summary>
+    public event EventHandler<EventArgs<int>>? ScrollableContentSizeChanged;
 
-    #region SliderPosition
+    #region Position
 
-    private void SliderOnPositionChanged (object? sender, EventArgs<int> e)
+    private int _position;
+
+    /// <summary>
+    ///     Gets or sets the position of the slider relative to <see cref="ScrollableContentSize"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         The content position is clamped to 0 and <see cref="ScrollableContentSize"/> minus <see cref="VisibleContentSize"/>.
+    ///     </para>
+    ///     <para>
+    ///         Setting will result in the <see cref="PositionChanging"/> and <see cref="PositionChanged"/>
+    ///         events being raised.
+    ///     </para>
+    /// </remarks>
+    public int Position
     {
-        if (ViewportDimension == 0)
+        get => _position;
+        set
         {
-            return;
-        }
+            if (value == _position)
+            {
+                return;
+            }
 
-        int pos = e.CurrentValue;
+            // Clamp the value between 0 and Size - VisibleContentSize
+            int newContentPosition = (int)Math.Clamp (value, 0, Math.Max (0, ScrollableContentSize - VisibleContentSize));
+            NavigationDirection direction = newContentPosition >= _position ? NavigationDirection.Forward : NavigationDirection.Backward;
 
-        RaiseSliderPositionChangeEvents (_slider.Position, pos);
-    }
+            if (OnPositionChanging (_position, newContentPosition))
+            {
+                return;
+            }
 
-    private void SliderOnScroll (object? sender, EventArgs<int> e)
-    {
-        if (ViewportDimension == 0)
-        {
-            return;
-        }
+            CancelEventArgs<int> args = new (ref _position, ref newContentPosition);
+            PositionChanging?.Invoke (this, args);
 
-        int calculatedSliderPos = CalculateSliderPosition (_contentPosition, e.CurrentValue >= 0 ? NavigationDirection.Forward : NavigationDirection.Backward);
-        int sliderScrolledAmount = e.CurrentValue;
-        int scrolledAmount = CalculateContentPosition (sliderScrolledAmount);
+            if (args.Cancel)
+            {
+                return;
+            }
 
-        RaiseSliderPositionChangeEvents (calculatedSliderPos, _slider.Position);
+            int distance = newContentPosition - _position;
 
-        ContentPosition = _contentPosition + scrolledAmount;
-    }
+            _position = newContentPosition;
 
-    /// <summary>
-    ///     Gets or sets the position of the start of the Scroll slider, within the Viewport.
-    /// </summary>
-    public int GetSliderPosition () => CalculateSliderPosition (_contentPosition);
+            OnPositionChanged (_position);
+            PositionChanged?.Invoke (this, new (in _position));
 
-    private void RaiseSliderPositionChangeEvents (int calculatedSliderPosition, int newSliderPosition)
-    {
-        if (calculatedSliderPosition == newSliderPosition)
-        {
-            return;
-        }
+            OnScrolled (distance);
+            Scrolled?.Invoke (this, new (in distance));
 
-        // This sets the slider position and clamps the value
-        _slider.Position = newSliderPosition;
+            int currentSliderPosition = _slider.Position;
 
-        OnSliderPositionChanged (newSliderPosition);
-        SliderPositionChanged?.Invoke (this, new (in newSliderPosition));
-    }
+            //_slider.Size = CalculateSliderSize ();
 
-    /// <summary>Called when the slider position has changed.</summary>
-    protected virtual void OnSliderPositionChanged (int position) { }
+            //int calculatedSliderPosition = CalculateSliderPosition (_position, direction);
 
-    /// <summary>Raised when the slider position has changed.</summary>
-    public event EventHandler<EventArgs<int>>? SliderPositionChanged;
+            //_slider.MoveToPosition (calculatedSliderPosition);
 
-    private int CalculateSliderPosition (int contentPosition, NavigationDirection direction = NavigationDirection.Forward)
-    {
-        if (Size - ViewportDimension == 0)
-        {
-            return 0;
-        }
+            _slider.Size = CalculateSliderSize ();
+            int calculatedSliderPosition = CalculateSliderPosition (_position, direction);
 
-        int scrollBarSize = Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
-        double newSliderPosition = (double)contentPosition / (Size - ViewportDimension) * (scrollBarSize - _slider.Size - _slider.ShrinkBy);
+            RaiseSliderPositionChangeEvents (calculatedSliderPosition, currentSliderPosition);
 
-        return direction == NavigationDirection.Forward ? (int)Math.Floor (newSliderPosition) : (int)Math.Ceiling (newSliderPosition);
+        }
     }
 
     /// <summary>
-    ///    INTERNAL API (for unit tests) - Calculates the content position based on the slider position. 
+    ///     Called when <see cref="Position"/> is changing. Return true to cancel the change.
+    /// </summary>
+    protected virtual bool OnPositionChanging (int currentPos, int newPos) { return false; }
+
+    /// <summary>
+    ///     Raised when the <see cref="Position"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
+    ///     <see langword="true"/> to prevent the position from being changed.
+    /// </summary>
+    public event EventHandler<CancelEventArgs<int>>? PositionChanging;
+
+    /// <summary>Called when <see cref="Position"/> has changed.</summary>
+    protected virtual void OnPositionChanged (int position) { }
+
+    /// <summary>Raised when the <see cref="Position"/> has changed.</summary>
+    public event EventHandler<EventArgs<int>>? PositionChanged;
+
+    /// <summary>Called when <see cref="Position"/> has changed. Indicates how much to scroll.</summary>
+    protected virtual void OnScrolled (int distance) { }
+
+    /// <summary>Raised when the <see cref="Position"/> has changed. Indicates how much to scroll.</summary>
+    public event EventHandler<EventArgs<int>>? Scrolled;
+
+
+    /// <summary>
+    ///    INTERNAL API (for unit tests) - Calculates the position within the <see cref="ScrollableContentSize"/> based on the slider position.
     /// </summary>
     /// <remarks>
     ///     Clamps the sliderPosition, ensuring the returned content position is always less than
-    ///     Size - VieportDimension.
+    ///     <see cref="ScrollableContentSize"/> - <see cref="VisibleContentSize"/>.
     /// </remarks>
     /// <param name="sliderPosition"></param>
     /// <returns></returns>
-    internal int CalculateContentPosition (int sliderPosition)
+    internal int CalculatePosition (int sliderPosition)
     {
         // Clamp the slider 
         int clampedSliderPosition = _slider.ClampPosition (sliderPosition);
         int scrollBarSize = Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
         double pos = (double)(clampedSliderPosition) /
-                     (scrollBarSize - _slider.Size - _slider.ShrinkBy) * (Size - ViewportDimension);
-        double rounded = Math.Round (pos);
+                     (scrollBarSize - _slider.Size - _slider.SliderPadding) * (ScrollableContentSize - VisibleContentSize);
+
+        if (pos is double.NaN)
+        {
+            return 0;
+        }
+        double rounded = Math.Ceiling (pos);
 
         // Clamp between 0 and Size - SliderSize
-        return (int)Math.Clamp (pos, 0, Math.Max (0, Size - _slider.Size));
+        return (int)Math.Clamp (rounded, 0, Math.Max (0, ScrollableContentSize - _slider.Size));
     }
 
+    #endregion ContentPosition
 
-    #endregion SliderPosition
-
-    #region ContentPosition
-
-    private int _contentPosition;
 
+    #region Slider Management
+    
     /// <summary>
-    ///     Gets or sets the position of the slider relative to <see cref="Size"/>.
+    ///     INTERNAL (for unit tests). Calculates the size of the slider based on the Orientation, VisibleContentSize, the actual Viewport, and Size.
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         The content position is clamped to 0 and <see cref="Size"/> minus <see cref="ViewportDimension"/>.
-    ///     </para>
-    ///     <para>
-    ///         Setting will result in the <see cref="ContentPositionChanging"/> and <see cref="ContentPositionChanged"/>
-    ///         events being raised.
-    ///     </para>
-    /// </remarks>
-    public int ContentPosition
+    /// <returns></returns>
+    internal int CalculateSliderSize ()
     {
-        get => _contentPosition;
-        set
+        int maxSliderSize = (Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width) - 2;
+
+        if (maxSliderSize < 1 || VisibleContentSize == 0)
         {
-            if (value == _contentPosition)
-            {
-                return;
-            }
+            return 1;
+        }
 
-            // Clamp the value between 0 and Size - ViewportDimension
-            int newContentPosition = (int)Math.Clamp (value, 0, Math.Max (0, Size - ViewportDimension));
-            NavigationDirection direction = newContentPosition >= _contentPosition ? NavigationDirection.Forward : NavigationDirection.Backward;
+        //if (ScrollableContentSize < VisibleContentSize)
+        //{
+        //    return maxSliderSize ;
+        //}
+        
+        int size = (int)Math.Clamp (Math.Floor ((double)VisibleContentSize / ScrollableContentSize * (maxSliderSize)), 1, VisibleContentSize);
+        return Math.Clamp (size, 1, maxSliderSize);
+    }
 
-            if (OnContentPositionChanging (_contentPosition, newContentPosition))
-            {
-                return;
-            }
+    private void SliderOnPositionChanged (object? sender, EventArgs<int> e)
+    {
+        if (VisibleContentSize == 0)
+        {
+            return;
+        }
 
-            CancelEventArgs<int> args = new (ref _contentPosition, ref newContentPosition);
-            ContentPositionChanging?.Invoke (this, args);
+        int pos = e.CurrentValue;
 
-            if (args.Cancel)
-            {
-                return;
-            }
+        RaiseSliderPositionChangeEvents (_slider.Position, pos);
+    }
 
-            int distance = newContentPosition - _contentPosition;
+    private void SliderOnScroll (object? sender, EventArgs<int> e)
+    {
+        if (VisibleContentSize == 0)
+        {
+            return;
+        }
 
-            _contentPosition = newContentPosition;
+        int calculatedSliderPos = CalculateSliderPosition (_position, e.CurrentValue >= 0 ? NavigationDirection.Forward : NavigationDirection.Backward);
+        int sliderScrolledAmount = e.CurrentValue;
+        int scrolledAmount = CalculatePosition (sliderScrolledAmount);
 
-            OnContentPositionChanged (_contentPosition);
-            ContentPositionChanged?.Invoke (this, new (in _contentPosition));
+        RaiseSliderPositionChangeEvents (calculatedSliderPos, _slider.Position);
 
-            OnScrolled (distance);
-            Scrolled?.Invoke (this, new (in distance));
+        Position = _position + scrolledAmount;
+    }
 
-            int currentSliderPosition = _slider.Position;
-            int calculatedSliderPosition = CalculateSliderPosition (_contentPosition, direction);
+    /// <summary>
+    ///     Gets or sets the position of the start of the Scroll slider, within the Viewport.
+    /// </summary>
+    public int GetSliderPosition () => CalculateSliderPosition (_position);
 
-            _slider.MoveToPosition (calculatedSliderPosition);
+    private void RaiseSliderPositionChangeEvents (int calculatedSliderPosition, int newSliderPosition)
+    {
+        if (calculatedSliderPosition == newSliderPosition)
+        {
+            return;
+        }
 
-            RaiseSliderPositionChangeEvents (currentSliderPosition, _slider.Position);
+        // This sets the slider position and clamps the value
+        _slider.Position = newSliderPosition;
 
-        }
+        OnSliderPositionChanged (newSliderPosition);
+        SliderPositionChanged?.Invoke (this, new (in newSliderPosition));
     }
 
-    /// <summary>
-    ///     Called when <see cref="ContentPosition"/> is changing. Return true to cancel the change.
-    /// </summary>
-    protected virtual bool OnContentPositionChanging (int currentPos, int newPos) { return false; }
+    /// <summary>Called when the slider position has changed.</summary>
+    protected virtual void OnSliderPositionChanged (int position) { }
+
+    /// <summary>Raised when the slider position has changed.</summary>
+    public event EventHandler<EventArgs<int>>? SliderPositionChanged;
 
     /// <summary>
-    ///     Raised when the <see cref="ContentPosition"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
-    ///     <see langword="true"/> to prevent the position from being changed.
+    ///    INTERNAL API (for unit tests) - Calculates the position of the slider based on the content position.
     /// </summary>
-    public event EventHandler<CancelEventArgs<int>>? ContentPositionChanging;
-
-    /// <summary>Called when <see cref="ContentPosition"/> has changed.</summary>
-    protected virtual void OnContentPositionChanged (int position) { }
+    /// <param name="contentPosition"></param>
+    /// <param name="direction"></param>
+    /// <returns></returns>
+    internal int CalculateSliderPosition (int contentPosition, NavigationDirection direction = NavigationDirection.Forward)
+    {
+        int scrollBarSize = Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
+        if (scrollBarSize < 3 || ScrollableContentSize - VisibleContentSize == 0)
+        {
+            return 0;
+        }
 
-    /// <summary>Raised when the <see cref="ContentPosition"/> has changed.</summary>
-    public event EventHandler<EventArgs<int>>? ContentPositionChanged;
+        double newSliderPosition = (double)(contentPosition - 1) / (ScrollableContentSize - VisibleContentSize) * (scrollBarSize - _slider.Size - _slider.SliderPadding);
 
-    /// <summary>Called when <see cref="ContentPosition"/> has changed. Indicates how much to scroll.</summary>
-    protected virtual void OnScrolled (int distance) { }
+        return Math.Clamp (direction == NavigationDirection.Forward ? (int)Math.Floor (newSliderPosition) : (int)Math.Ceiling (newSliderPosition), 0, scrollBarSize - _slider.Size - _slider.SliderPadding);
+    }
 
-    /// <summary>Raised when the <see cref="ContentPosition"/> has changed. Indicates how much to scroll.</summary>
-    public event EventHandler<EventArgs<int>>? Scrolled;
 
-    #endregion ContentPosition
+    #endregion Slider Management
 
     /// <inheritdoc/>
     protected override bool OnClearingViewport ()
@@ -531,38 +572,28 @@ public class ScrollBar : View, IOrientation, IDesignable
 
 #if PROPORTIONAL_SCROLL_JUMP
         // BUGBUG: This logic mostly works to provide a proportional jump. However, the math
-        // BUGBUG: falls apart in edge cases. Most other scroll bars (e.g. Windows) do not do prooportional
+        // BUGBUG: falls apart in edge cases. Most other scroll bars (e.g. Windows) do not do proportional
         // BUGBUG: Thus, this is disabled and we just jump a page each click.
         // Ratio of the distance to the viewport dimension
-        double ratio = (double)Math.Abs (distanceFromCenter) / (ViewportDimension);
+        double ratio = (double)Math.Abs (distanceFromCenter) / (VisibleContentSize);
         // Jump size based on the ratio and the total content size
-        int jump = (int)(ratio * (Size - ViewportDimension));
+        int jump = (int)(ratio * (Size - VisibleContentSize));
 #else
-        int jump = (ViewportDimension);
+        int jump = (VisibleContentSize);
 #endif
         // Adjust the content position based on the distance
         if (distanceFromCenter < 0)
         {
-            ContentPosition = Math.Max (0, ContentPosition - jump);
+            Position = Math.Max (0, Position - jump);
         }
         else
         {
-            ContentPosition = Math.Min (Size - _slider.ViewportDimension, ContentPosition + jump);
+            Position = Math.Min (ScrollableContentSize - _slider.VisibleContentSize, Position + jump);
         }
 
         return true;
     }
 
-
-
-    /// <summary>
-    ///     Gets or sets the amount each mouse hweel event will incremenet/decrement the <see cref="ContentPosition"/>.
-    /// </summary>
-    /// <remarks>
-    ///     The default is 1.
-    /// </remarks>
-    public int Increment { get; set; } = 1;
-
     /// <inheritdoc/>
     protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
     {
@@ -580,24 +611,24 @@ public class ScrollBar : View, IOrientation, IDesignable
         {
             if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledDown))
             {
-                ContentPosition += Increment;
+                Position += Increment;
             }
 
             if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledUp))
             {
-                ContentPosition -= Increment;
+                Position -= Increment;
             }
         }
         else
         {
             if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledRight))
             {
-                ContentPosition += Increment;
+                Position += Increment;
             }
 
             if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledLeft))
             {
-                ContentPosition -= Increment;
+                Position -= Increment;
             }
         }
 
@@ -623,7 +654,7 @@ public class ScrollBar : View, IOrientation, IDesignable
 
         Width = 1;
         Height = Dim.Fill ();
-        Size = 250;
+        ScrollableContentSize = 250;
 
         return true;
     }

+ 59 - 43
Terminal.Gui/Views/ScrollBar/ScrollSlider.cs

@@ -40,7 +40,14 @@ public class ScrollSlider : View, IOrientation, IDesignable
 
         FrameChanged += (sender, args) =>
                         {
-                            
+                            //if (Orientation == Orientation.Vertical)
+                            //{
+                            //    Size = Frame.Height;
+                            //}
+                            //else
+                            //{
+                            //    Size = Frame.Width;
+                            //}
                         };
 
         SubviewLayout += (sender, args) =>
@@ -49,7 +56,7 @@ public class ScrollSlider : View, IOrientation, IDesignable
 
         SubviewsLaidOut += (sender, args) =>
                            {
-                               
+
                            };
     }
 
@@ -85,10 +92,12 @@ public class ScrollSlider : View, IOrientation, IDesignable
         // Reset opposite dim to Dim.Fill ()
         if (Orientation == Orientation.Vertical)
         {
+            Height = Width;
             Width = Dim.Fill ();
         }
         else
         {
+            Width = Height;
             Height = Dim.Fill ();
         }
         SetNeedsLayout ();
@@ -149,7 +158,7 @@ public class ScrollSlider : View, IOrientation, IDesignable
                 return;
             }
 
-            _size = Math.Clamp (value, 1, ViewportDimension);
+            _size = Math.Clamp (value, 1, VisibleContentSize);
 
 
             if (Orientation == Orientation.Vertical)
@@ -164,32 +173,32 @@ public class ScrollSlider : View, IOrientation, IDesignable
         }
     }
 
-    private int? _viewportDimension;
+    private int? _visibleContentSize;
 
     /// <summary>
     ///     Gets or sets the size of the viewport into the content being scrolled. If not explicitly set, will be the
     ///     greater of 1 and the dimension of the <see cref="View.SuperView"/>.
     /// </summary>
-    public int ViewportDimension
+    public int VisibleContentSize
     {
         get
         {
-            if (_viewportDimension.HasValue)
+            if (_visibleContentSize.HasValue)
             {
-                return _viewportDimension.Value;
+                return _visibleContentSize.Value;
             }
 
             return Math.Max (1, Orientation == Orientation.Vertical ? SuperView?.Viewport.Height ?? 2048 : SuperView?.Viewport.Width ?? 2048);
         }
         set
         {
-            if (value == _viewportDimension)
+            if (value == _visibleContentSize)
             {
                 return;
             }
-            _viewportDimension = int.Max (1, value);
-            
-            if (_position > _viewportDimension - _size)
+            _visibleContentSize = int.Max (1, value);
+
+            if (_position > _visibleContentSize - _size)
             {
                 Position = _position;
             }
@@ -229,11 +238,11 @@ public class ScrollSlider : View, IOrientation, IDesignable
     {
         if (Orientation == Orientation.Vertical)
         {
-            Y = _position + ShrinkBy / 2;
+            Y = _position + SliderPadding / 2;
         }
         else
         {
-            X = _position + ShrinkBy / 2;
+            X = _position + SliderPadding / 2;
         }
     }
 
@@ -245,7 +254,7 @@ public class ScrollSlider : View, IOrientation, IDesignable
     /// <returns></returns>
     internal int ClampPosition (int newPosition)
     {
-        return Math.Clamp (newPosition, 0, Math.Max (0, ViewportDimension - Size));
+        return Math.Clamp (newPosition, 0, Math.Max (0, VisibleContentSize - SliderPadding - Size));
     }
 
     private void RaisePositionChangeEvents (int newPosition)
@@ -333,7 +342,14 @@ public class ScrollSlider : View, IOrientation, IDesignable
     ///// <inheritdoc/>
     private int _lastLocation = -1;
 
-    public int ShrinkBy { get; set; }
+    /// <summary>
+    ///     Gets or sets the amount to pad the start and end of the scroll slider. The default is 0.
+    /// </summary>
+    /// <remarks>
+    ///     When the scroll slider is used by <see cref="ScrollBar"/>, which has increment and decrement buttons, the
+    ///     SliderPadding should be set to the size of the buttons (typically 2).
+    /// </remarks>
+    public int SliderPadding { get; set; }
 
     /// <inheritdoc/>
     protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
@@ -348,9 +364,9 @@ public class ScrollSlider : View, IOrientation, IDesignable
             return true;
         }
 
-        int location = (Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X) + ShrinkBy / 2;
-        int offset = _lastLocation > -1 ? location - _lastLocation : 0;
-        int superViewDimension = ViewportDimension;
+        int location = (Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X);
+        int offsetFromLastLocation = _lastLocation > -1 ? location - _lastLocation : 0;
+        int superViewDimension = VisibleContentSize;
 
         if (mouseEvent.IsPressed || mouseEvent.IsReleased)
         {
@@ -364,22 +380,35 @@ public class ScrollSlider : View, IOrientation, IDesignable
             }
             else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
             {
+                int currentLocation;
                 if (Orientation == Orientation.Vertical)
                 {
-                    Y = Frame.Y + offset < ShrinkBy / 2
-                            ? ShrinkBy / 2
-                            : Frame.Y + offset + Frame.Height > superViewDimension
-                                ? Math.Max (superViewDimension - Frame.Height + ShrinkBy, 1)
-                                : Frame.Y + offset;
+                    currentLocation = Frame.Y;
                 }
                 else
                 {
-                    X = Frame.X + offset < ShrinkBy / 2
-                            ? ShrinkBy / 2
-                            : Frame.X + offset + Frame.Width > superViewDimension
-                                ? Math.Max (superViewDimension - Frame.Width + ShrinkBy / 2, 1)
-                                : Frame.X + offset;
+                    currentLocation = Frame.X;
                 }
+
+                // location does not account for the ShrinkBy
+                int sliderLowerBound = SliderPadding / 2;
+                int sliderUpperBound = superViewDimension - SliderPadding / 2 - Size;
+
+                int newLocation = currentLocation + offsetFromLastLocation;
+                Position = newLocation;
+
+                //if (location > 0 && location < sliderLowerBound)
+                //{
+                //    Position = 0;
+                //}
+                //else if (location > sliderUpperBound)
+                //{
+                //    Position = superViewDimension - Size;
+                //}
+                //else
+                //{
+                //    Position = currentLocation + offsetFromLastLocation;
+                //}
             }
             else if (mouseEvent.Flags == MouseFlags.Button1Released)
             {
@@ -400,22 +429,9 @@ public class ScrollSlider : View, IOrientation, IDesignable
     /// <inheritdoc/>
     public bool EnableForDesign ()
     {
-        OrientationChanged += (sender, args) =>
-                              {
-                                  if (args.CurrentValue == Orientation.Vertical)
-                                  {
-                                      Width = Dim.Fill ();
-                                      Size = 5;
-                                  }
-                                  else
-                                  {
-                                      Size = 5;
-                                      Height = Dim.Fill ();
-                                  }
-                              };
-
-        Orientation = Orientation.Horizontal;
+//        Orientation = Orientation.Horizontal;
         ShowPercent = true;
+        Size = 5;
 
         return true;
     }

+ 10 - 10
UICatalog/Scenarios/CharacterMap/CharMap.cs

@@ -149,7 +149,7 @@ public class CharMap : View, IDesignable
             Y = Pos.AnchorEnd (),
             Orientation = Orientation.Horizontal,
             Width = Dim.Fill (1),
-            Size = GetContentSize ().Width - RowLabelWidth,
+            ScrollableContentSize = GetContentSize ().Width - RowLabelWidth,
             Increment = COLUMN_WIDTH,
         };
 
@@ -159,12 +159,12 @@ public class CharMap : View, IDesignable
             X = Pos.AnchorEnd (),
             Y = 1, // Header
             Height = Dim.Fill (Dim.Func (() => Padding.Thickness.Bottom)),
-            Size = GetContentSize ().Height
+            ScrollableContentSize = GetContentSize ().Height
         };
 
         Padding.Add (_vScrollBar, _hScrollBar);
 
-        _vScrollBar.ContentPositionChanged += (sender, args) =>
+        _vScrollBar.PositionChanged += (sender, args) =>
                                               {
                                                   if (Viewport.Height > 0)
                                                   {
@@ -179,7 +179,7 @@ public class CharMap : View, IDesignable
                                               {
                                                   //ScrollVertical (args.CurrentValue);
                                               };
-        _hScrollBar.ContentPositionChanged += (sender, args) =>
+        _hScrollBar.PositionChanged += (sender, args) =>
                                               {
                                                   if (Viewport.Width > 0)
                                                   {
@@ -234,8 +234,8 @@ public class CharMap : View, IDesignable
             ScrollHorizontal (newCursor.X - Viewport.Width + 1);
         }
 
-        _vScrollBar.ContentPosition = Viewport.Y;
-        _hScrollBar.ContentPosition = Viewport.X;
+        _vScrollBar.Position = Viewport.Y;
+        _hScrollBar.Position = Viewport.X;
     }
 
     #region Cursor
@@ -524,7 +524,7 @@ public class CharMap : View, IDesignable
         if (e.Flags == MouseFlags.WheeledDown)
         {
             ScrollVertical (1);
-            _vScrollBar.ContentPosition = Viewport.Y;
+            _vScrollBar.Position = Viewport.Y;
             e.Handled = true;
 
             return;
@@ -533,7 +533,7 @@ public class CharMap : View, IDesignable
         if (e.Flags == MouseFlags.WheeledUp)
         {
             ScrollVertical (-1);
-            _vScrollBar.ContentPosition = Viewport.Y;
+            _vScrollBar.Position = Viewport.Y;
             e.Handled = true;
 
             return;
@@ -542,7 +542,7 @@ public class CharMap : View, IDesignable
         if (e.Flags == MouseFlags.WheeledRight)
         {
             ScrollHorizontal (1);
-            _hScrollBar.ContentPosition = Viewport.X;
+            _hScrollBar.Position = Viewport.X;
             e.Handled = true;
 
             return;
@@ -551,7 +551,7 @@ public class CharMap : View, IDesignable
         if (e.Flags == MouseFlags.WheeledLeft)
         {
             ScrollHorizontal (-1);
-            _hScrollBar.ContentPosition = Viewport.X;
+            _hScrollBar.Position = Viewport.X;
             e.Handled = true;
         }
     }

+ 24 - 24
UICatalog/Scenarios/ScrollBarDemo.cs

@@ -36,7 +36,7 @@ public class ScrollBarDemo : Scenario
         {
             X = Pos.AnchorEnd () - 5,
             AutoHide = false,
-            Size = 100,
+            ScrollableContentSize = 100,
             //ShowPercent = true
         };
         demoFrame.Add (scrollBar);
@@ -52,7 +52,7 @@ public class ScrollBarDemo : Scenario
         demoFrame.Add (controlledList);
 
         // populate the list box with Size items of the form "{n:00000}"
-        controlledList.SetSource (new ObservableCollection<string> (Enumerable.Range (0, scrollBar.Size).Select (n => $"{n:00000}")));
+        controlledList.SetSource (new ObservableCollection<string> (Enumerable.Range (0, scrollBar.ScrollableContentSize).Select (n => $"{n:00000}")));
 
         int GetMaxLabelWidth (int groupId)
         {
@@ -167,7 +167,7 @@ public class ScrollBarDemo : Scenario
 
         NumericUpDown<int> scrollSize = new ()
         {
-            Value = scrollBar.Size,
+            Value = scrollBar.ScrollableContentSize,
             X = Pos.Right (lblSize) + 1,
             Y = Pos.Top (lblSize)
         };
@@ -182,30 +182,30 @@ public class ScrollBarDemo : Scenario
                                             return;
                                         }
 
-                                        if (scrollBar.Size != e.NewValue)
+                                        if (scrollBar.ScrollableContentSize != e.NewValue)
                                         {
-                                            scrollBar.Size = e.NewValue;
+                                            scrollBar.ScrollableContentSize = e.NewValue;
                                         }
                                     };
 
-        var lblViewportDimension = new Label
+        var lblVisibleContentSize = new Label
         {
-            Text = "_ViewportDimension::",
+            Text = "_VisibleContentSize::",
             TextAlignment = Alignment.End,
             Y = Pos.Align (Alignment.Start, groupId: 1),
             Width = Dim.Func (() => GetMaxLabelWidth (1))
         };
-        demoFrame.Add (lblViewportDimension);
+        demoFrame.Add (lblVisibleContentSize);
 
-        NumericUpDown<int> viewportDimension = new ()
+        NumericUpDown<int> VisibleContentSize = new ()
         {
-            Value = scrollBar.ViewportDimension,
-            X = Pos.Right (lblViewportDimension) + 1,
-            Y = Pos.Top (lblViewportDimension)
+            Value = scrollBar.VisibleContentSize,
+            X = Pos.Right (lblVisibleContentSize) + 1,
+            Y = Pos.Top (lblVisibleContentSize)
         };
-        demoFrame.Add (viewportDimension);
+        demoFrame.Add (VisibleContentSize);
 
-        viewportDimension.ValueChanging += (s, e) =>
+        VisibleContentSize.ValueChanging += (s, e) =>
                                            {
                                                if (e.NewValue < 0)
                                                {
@@ -214,9 +214,9 @@ public class ScrollBarDemo : Scenario
                                                    return;
                                                }
 
-                                               if (scrollBar.ViewportDimension != e.NewValue)
+                                               if (scrollBar.VisibleContentSize != e.NewValue)
                                                {
-                                                   scrollBar.ViewportDimension = e.NewValue;
+                                                   scrollBar.VisibleContentSize = e.NewValue;
                                                }
                                            };
 
@@ -281,12 +281,12 @@ public class ScrollBarDemo : Scenario
                                                        return;
                                                    }
 
-                                                   if (scrollBar.ContentPosition != e.NewValue)
+                                                   if (scrollBar.Position != e.NewValue)
                                                    {
-                                                       scrollBar.ContentPosition = e.NewValue;
+                                                       scrollBar.Position = e.NewValue;
                                                    }
 
-                                                   if (scrollBar.ContentPosition != e.NewValue)
+                                                   if (scrollBar.Position != e.NewValue)
                                                    {
                                                        e.Cancel = true;
                                                    }
@@ -351,7 +351,7 @@ public class ScrollBarDemo : Scenario
                                          lblScrollFrame.Text = $"Scroll Frame: {scrollBar.Frame.ToString ()}";
                                          lblScrollViewport.Text = $"Scroll Viewport: {scrollBar.Viewport.ToString ()}";
                                          lblScrollContentSize.Text = $"Scroll ContentSize: {scrollBar.GetContentSize ().ToString ()}";
-                                         viewportDimension.Value = scrollBar.ViewportDimension;
+                                         VisibleContentSize.Value = scrollBar.VisibleContentSize;
                                      };
 
         EventLog eventLog = new ()
@@ -368,7 +368,7 @@ public class ScrollBarDemo : Scenario
 
         void AppOnInitialized (object sender, EventArgs e)
         {
-            scrollBar.SizeChanged += (s, e) =>
+            scrollBar.ScrollableContentSizeChanged += (s, e) =>
                                   {
                                       eventLog.Log ($"SizeChanged: {e.CurrentValue}");
 
@@ -381,7 +381,7 @@ public class ScrollBarDemo : Scenario
             scrollBar.SliderPositionChanged += (s, e) =>
                                             {
                                                 eventLog.Log ($"SliderPositionChanged: {e.CurrentValue}");
-                                                eventLog.Log ($"  ContentPosition: {scrollBar.ContentPosition}");
+                                                eventLog.Log ($"  ContentPosition: {scrollBar.Position}");
                                                 scrollSliderPosition.Text = e.CurrentValue.ToString ();
                                             };
 
@@ -392,7 +392,7 @@ public class ScrollBarDemo : Scenario
                                    scrolled.Text = e.CurrentValue.ToString ();
                                };
 
-            scrollBar.ContentPositionChanged += (s, e) =>
+            scrollBar.PositionChanged += (s, e) =>
                                              {
                                                  eventLog.Log ($"ContentPositionChanged: {e.CurrentValue}");
                                                  scrollContentPosition.Value = e.CurrentValue;
@@ -403,7 +403,7 @@ public class ScrollBarDemo : Scenario
             controlledList.ViewportChanged += (s, e) =>
                                               {
                                                   eventLog.Log ($"ViewportChanged: {e.NewViewport.Y}");
-                                                  scrollBar.ContentPosition = e.NewViewport.Y;
+                                                  scrollBar.Position = e.NewViewport.Y;
                                               };
 
         }

+ 602 - 241
UnitTests/Views/ScrollBarTests.cs

@@ -3,33 +3,166 @@ using static Unix.Terminal.Delegates;
 
 namespace Terminal.Gui.ViewsTests;
 
-public class ScrollBarTests
+public class ScrollBarTests (ITestOutputHelper output)
 {
-    public ScrollBarTests (ITestOutputHelper output) { _output = output; }
-    private readonly ITestOutputHelper _output;
+    [Fact]
+    public void Constructor_Defaults ()
+    {
+        var scrollBar = new ScrollBar ();
+        Assert.False (scrollBar.CanFocus);
+        Assert.Equal (Orientation.Vertical, scrollBar.Orientation);
+        Assert.Equal (0, scrollBar.ScrollableContentSize);
+        Assert.Equal (0, scrollBar.VisibleContentSize);
+        Assert.Equal (0, scrollBar.GetSliderPosition ());
+        Assert.Equal (0, scrollBar.Position);
+        Assert.True (scrollBar.AutoHide);
+    }
 
+    #region AutoHide
     [Fact]
     [AutoInitShutdown]
-    public void AutoHideScrollBar_CheckScrollBarVisibility ()
+    public void AutoHide_True_Is_Default_CorrectlyHidesAndShows ()
     {
-        var scrollBar = new ScrollBar { Width = 2, Height = Dim.Fill (), Size = 30 };
-        View scrollBarSuperView = ScrollBarSuperView ();
-        scrollBarSuperView.Add (scrollBar);
-        Application.Begin ((scrollBarSuperView.SuperView as Toplevel)!);
+        var super = new Toplevel ()
+        {
+            Id = "super",
+            Width = 1,
+            Height = 20
+        };
+
+        var scrollBar = new ScrollBar
+        {
+            ScrollableContentSize = 20,
+        };
+        super.Add (scrollBar);
+        Assert.True (scrollBar.AutoHide);
+        Assert.True (scrollBar.Visible); // Before Init
+
+        RunState rs = Application.Begin (super);
+
+        // Should Show
+        scrollBar.ScrollableContentSize = 21;
+        Application.RunIteration (ref rs);
+        Assert.True (scrollBar.Visible);
+
+        // Should Hide
+        scrollBar.ScrollableContentSize = 10;
+        Assert.False (scrollBar.Visible);
+
+        super.Dispose ();
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void AutoHide_False_CorrectlyHidesAndShows ()
+    {
+        var super = new Toplevel ()
+        {
+            Id = "super",
+            Width = 1,
+            Height = 20
+        };
+
+        var scrollBar = new ScrollBar
+        {
+            ScrollableContentSize = 20,
+            AutoHide = false
+        };
+        super.Add (scrollBar);
+        Assert.False (scrollBar.AutoHide);
+        Assert.True (scrollBar.Visible);
+
+        RunState rs = Application.Begin (super);
+
+        // Should Hide if AutoSize = true, but should not hide if AutoSize = false
+        scrollBar.ScrollableContentSize = 10;
+        Assert.True (scrollBar.Visible);
+
+        super.Dispose ();
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void AutoHide_Change_AutoSize_CorrectlyHidesAndShows ()
+    {
+        var super = new Toplevel ()
+        {
+            Id = "super",
+            Width = 1,
+            Height = 20
+        };
+
+        var scrollBar = new ScrollBar
+        {
+            ScrollableContentSize = 20,
+        };
+        super.Add (scrollBar);
+        Assert.True (scrollBar.AutoHide);
+        Assert.True (scrollBar.Visible); // Before Init
+
+        RunState rs = Application.Begin (super);
+
+        Assert.False (scrollBar.Visible);
+        Assert.Equal (1, scrollBar.Frame.Width);
+        Assert.Equal (20, scrollBar.Frame.Height);
+
+        scrollBar.ScrollableContentSize = 10;
+        Application.RunIteration (ref rs);
+        Assert.False (scrollBar.Visible);
+
+        scrollBar.ScrollableContentSize = 30;
+        Assert.True (scrollBar.Visible);
+
+        scrollBar.AutoHide = false;
+        Assert.True (scrollBar.Visible);
+
+        scrollBar.ScrollableContentSize = 10;
+        Assert.True (scrollBar.Visible);
+
+        super.Dispose ();
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void AutoHide_Change_Size_CorrectlyHidesAndShows ()
+    {
+        var super = new Toplevel ()
+        {
+            Id = "super",
+            Width = 1,
+            Height = 20
+        };
+
+        var scrollBar = new ScrollBar
+        {
+            ScrollableContentSize = 20,
+        };
+        super.Add (scrollBar);
+
+        RunState rs = Application.Begin (super);
 
         Assert.Equal (Orientation.Vertical, scrollBar.Orientation);
+        Assert.Equal (20, scrollBar.VisibleContentSize);
+        //Assert.True (scrollBar.ShowScrollIndicator);
+        Assert.False (scrollBar.Visible);
+        Assert.Equal (1, scrollBar.Frame.Width);
+        Assert.Equal (20, scrollBar.Frame.Height);
+
+        scrollBar.ScrollableContentSize = 10;
+        Application.RunIteration (ref rs);
+        //Assert.False (scrollBar.ShowScrollIndicator);
+        Assert.False (scrollBar.Visible);
+
+        scrollBar.ScrollableContentSize = 30;
         //Assert.True (scrollBar.ShowScrollIndicator);
         Assert.True (scrollBar.Visible);
-        Assert.Equal ("Absolute(2)", scrollBar.Width!.ToString ());
-        Assert.Equal (2, scrollBar.Viewport.Width);
-        Assert.Equal ("Fill(Absolute(0))", scrollBar.Height!.ToString ());
-        Assert.Equal (25, scrollBar.Viewport.Height);
 
-        scrollBar.Size = 10;
+        scrollBar.ScrollableContentSize = 10;
+        Application.RunIteration (ref rs);
         //Assert.False (scrollBar.ShowScrollIndicator);
         Assert.False (scrollBar.Visible);
 
-        scrollBar.Size = 30;
+        scrollBar.ScrollableContentSize = 21;
         //Assert.True (scrollBar.ShowScrollIndicator);
         Assert.True (scrollBar.Visible);
 
@@ -37,37 +170,25 @@ public class ScrollBarTests
         //Assert.True (scrollBar.ShowScrollIndicator);
         Assert.True (scrollBar.Visible);
 
-        scrollBar.Size = 10;
+        scrollBar.ScrollableContentSize = 10;
         //Assert.True (scrollBar.ShowScrollIndicator);
         Assert.True (scrollBar.Visible);
 
-        scrollBarSuperView.SuperView!.Dispose ();
-    }
-
-
-    [Fact]
-    public void Constructor_Defaults ()
-    {
-        var scrollBar = new ScrollBar ();
-        Assert.False (scrollBar.CanFocus);
-        Assert.Equal (Orientation.Vertical, scrollBar.Orientation);
-        Assert.Equal (0, scrollBar.Size);
-        Assert.Equal (0, scrollBar.ViewportDimension);
-        Assert.Equal (0, scrollBar.GetSliderPosition ());
-        Assert.Equal (0, scrollBar.ContentPosition);
-        Assert.True (scrollBar.AutoHide);
+        super.Dispose ();
     }
 
+    #endregion AutoHide
 
+    #region Orientation
     [Fact]
     public void OnOrientationChanged_Keeps_Size ()
     {
         var scroll = new ScrollBar ();
         scroll.Layout ();
-        scroll.Size = 1;
+        scroll.ScrollableContentSize = 1;
 
         scroll.Orientation = Orientation.Horizontal;
-        Assert.Equal (1, scroll.Size);
+        Assert.Equal (1, scroll.ScrollableContentSize);
     }
 
     [Fact]
@@ -84,23 +205,391 @@ public class ScrollBarTests
         };
         super.Add (scrollBar);
         scrollBar.Layout ();
-        scrollBar.ContentPosition = 1;
+        scrollBar.Position = 1;
         scrollBar.Orientation = Orientation.Horizontal;
 
         Assert.Equal (0, scrollBar.GetSliderPosition ());
     }
 
+    #endregion Orientation
+
+    #region Slider
+
+    [Theory]
+    [InlineData (-1, 10, 1)]
+    [InlineData (0, 10, 1)]
+    [InlineData (10, 15, 5)]
+    [InlineData (10, 5, 8)]
+    [InlineData (10, 3, 8)]
+    [InlineData (10, 2, 8)]
+    [InlineData (10, 1, 8)]
+    [InlineData (10, 0, 8)]
+    [InlineData (10, 10, 8)]
+    [InlineData (10, 20, 4)]
+    [InlineData (10, 100, 1)]
+    [InlineData (15, 0, 13)]
+    [InlineData (15, 1, 13)]
+    [InlineData (15, 2, 13)]
+    [InlineData (15, 3, 13)]
+    [InlineData (15, 5, 13)]
+    [InlineData (15, 10, 13)]
+    [InlineData (15, 14, 13)]
+    [InlineData (15, 15, 13)]
+    [InlineData (15, 16, 12)]
+    [InlineData (20, 10, 18)]
+    [InlineData (100, 10, 98)]
+    public void CalculateSliderSize_Width_Is_VisibleContentSize_CalculatesCorrectly (int visibleContentSize, int scrollableContentSize, int expectedSliderSize)
+    {
+        // Arrange
+        var scrollBar = new ScrollBar
+        {
+            VisibleContentSize = visibleContentSize,
+            ScrollableContentSize = scrollableContentSize,
+            Orientation = Orientation.Horizontal // Assuming horizontal for simplicity
+        };
+        scrollBar.Width = visibleContentSize;
+
+        // Act
+        var sliderSize = scrollBar.CalculateSliderSize ();
+
+        // Assert
+        Assert.Equal (expectedSliderSize, sliderSize);
+    }
+
+    [Theory]
+    // 0123456789
+    //  -
+    // **********
+    // ◄███►
+    [InlineData (5, 10, 1, 3)]
+
+    // 01234567890
+    //  ----------
+    // **********
+    // ◄██░►
+    [InlineData (5, 10, 11, 2)]
+
+
+    [InlineData (20, 10, 1, 18)]
+
+    //// ◄█░░░░░░░►
+    //[InlineData (1, 10, 1)]
+
+    ////  ---------
+    //// ◄████░░░░►
+    //[InlineData (5, 10, 4)]
+
+    ////  ----------
+    //// ◄███░░░░░►
+    //[InlineData (5, 11, 3)]
+    //[InlineData (5, 12, 3)]
+    //[InlineData (5, 13, 3)]
+
+    //// 012345678901234
+    ////  --------------
+    //// ◄██░░░░░░►
+    //[InlineData (5, 14, 2)]
+    //[InlineData (5, 15, 2)]
+    //[InlineData (5, 16, 2)]
+
+    //// 012345678901234567890
+    ////  ----------------
+    //// ◄██░░░░░░►
+    //[InlineData (5, 18, 2)]
+    //[InlineData (5, 19, 2)]
+    //[InlineData (5, 20, 2)]
+
+
+    //// 012345678901234567890
+    ////  --------------------
+    //// ◄█░░░░░░░►
+    //[InlineData (5, 21, 1)]
+    //[InlineData (5, 22, 1)]
+    //[InlineData (5, 23, 1)]
+    public void CalculateSliderSize_Width_Is_LT_VisibleContentSize_CalculatesCorrectly (int width, int visibleContentSize, int scrollableContentSize, int expectedSliderSize)
+    {
+        // Arrange
+        var scrollBar = new ScrollBar
+        {
+            VisibleContentSize = visibleContentSize,
+            ScrollableContentSize = scrollableContentSize,
+            Orientation = Orientation.Horizontal // Assuming horizontal for simplicity
+        };
+        scrollBar.Width = width;
+
+        // Act
+        var sliderSize = scrollBar.CalculateSliderSize ();
+
+        // Assert
+        Assert.Equal (expectedSliderSize, sliderSize);
+    }
+
+
+    [Theory]
+    // 0123456789
+    //  ---------
+    // ◄█░░░░░░░►
+    [InlineData (0, 10, 1)]
+    // ◄█░░░░░░░►
+    [InlineData (1, 10, 1)]
+
+    //  ---------
+    // ◄████░░░░►
+    [InlineData (5, 10, 4)]
+
+    //  ----------
+    // ◄███░░░░░►
+    [InlineData (5, 11, 3)]
+    [InlineData (5, 12, 3)]
+    [InlineData (5, 13, 3)]
+
+    // 012345678901234
+    //  --------------
+    // ◄██░░░░░░►
+    [InlineData (5, 14, 2)]
+    [InlineData (5, 15, 2)]
+    [InlineData (5, 16, 2)]
+
+    // 012345678901234567890
+    //  ----------------
+    // ◄██░░░░░░►
+    [InlineData (5, 18, 2)]
+    [InlineData (5, 19, 2)]
+    [InlineData (5, 20, 2)]
+
+
+    // 012345678901234567890
+    //  --------------------
+    // ◄█░░░░░░░►
+    [InlineData (5, 21, 1)]
+    [InlineData (5, 22, 1)]
+    [InlineData (5, 23, 1)]
+
+    public void CalculateSliderSize_Width_Is_GT_VisibleContentSize_CalculatesCorrectly (int visibleContentSize, int scrollableContentSize, int expectedSliderSize)
+    {
+        // Arrange
+        var scrollBar = new ScrollBar
+        {
+            VisibleContentSize = visibleContentSize,
+            ScrollableContentSize = scrollableContentSize,
+            Orientation = Orientation.Horizontal // Assuming horizontal for simplicity
+        };
+        scrollBar.Width = 10;
+
+        // Act
+        var sliderSize = scrollBar.CalculateSliderSize ();
+
+        // Assert
+        Assert.Equal (expectedSliderSize, sliderSize);
+    }
+
+    [Theory]
+    // 0123456789
+    //  ---------
+    // ◄█►
+    [InlineData (3, 3, 0, 0)]
+    [InlineData (3, 3, 1, 0)]
+    [InlineData (3, 3, 2, 0)]
+
+    // 0123456789
+    //  ---------
+    // ◄██►
+    [InlineData (4, 4, 0, 0)]
+    [InlineData (4, 4, 1, 0)]
+    [InlineData (4, 4, 2, 0)]
+    [InlineData (4, 4, 3, 0)]
+    [InlineData (4, 4, 4, 0)]
+
+
+    // 012345
+    //  ^----
+    // ◄█░►
+    [InlineData (4, 5, 0, 0)]
+    //  -^---
+    // ◄█░►
+    [InlineData (4, 5, 1, 0)]
+    //  --^--
+    // ◄░█►
+    [InlineData (4, 5, 2, 1)]
+    //  ---^-
+    // ◄░█►
+    [InlineData (4, 5, 3, 1)]
+    //  ----^
+    // ◄░█►
+    [InlineData (4, 5, 4, 1)]
+
+    // 01234
+    // ^---------
+    // ◄█░░►
+    [InlineData (5, 10, 0, 0)]
+    // -^--------
+    // ◄█░░►
+    [InlineData (5, 10, 1, 0)]
+    // --^-------
+    // ◄█░░►
+    [InlineData (5, 10, 2, 0)]
+    // ---^------
+    // ◄█░░►
+    [InlineData (5, 10, 3, 0)]
+    // ----^----
+    // ◄░█░►
+    [InlineData (5, 10, 4, 1)]
+    // -----^---
+    // ◄░█░►
+    [InlineData (5, 10, 5, 1)]
+    // ------^--
+    // ◄░░█►
+    [InlineData (5, 10, 6, 2)]
+    // ------^--
+    // ◄░░█►
+    [InlineData (5, 10, 7, 2)]
+    // -------^-
+    // ◄░░█►
+    [InlineData (5, 10, 8, 2)]
+    // --------^
+    // ◄░░█►
+    [InlineData (5, 10, 9, 2)]
+
+
+    [InlineData (10, 20, 0, 0)]
+    [InlineData (10, 20, 1, 0)]
+    [InlineData (10, 20, 2, 0)]
+    [InlineData (10, 20, 3, 1)]
+    [InlineData (10, 20, 4, 2)]
+    [InlineData (10, 20, 5, 2)]
+    [InlineData (10, 20, 6, 3)]
+    [InlineData (10, 20, 7, 4)]
+    [InlineData (10, 20, 8, 4)]
+
+    public void CalculateSliderPosition_Calculates_Correctly (int visibleContentSize, int scrollableContentSize,  int contentPosition, int expectedSliderPosition)
+    {
+        // Arrange
+        var scrollBar = new ScrollBar
+        {
+            ScrollableContentSize = scrollableContentSize,
+            VisibleContentSize = visibleContentSize,
+            Orientation = Orientation.Horizontal // Assuming horizontal for simplicity
+        };
+        scrollBar.Width = visibleContentSize;
+
+        // Act
+        var sliderPosition= scrollBar.CalculateSliderPosition (contentPosition, NavigationDirection.Forward);
+
+        // Assert
+        Assert.Equal (expectedSliderPosition, sliderPosition);
+    }
+
 
+    #endregion Slider
+
+    #region Size
+
+    // TODO: Add tests.
+
+    #endregion Size
+
+    #region Position
+
+    // 012345678901
+    // ◄█░░░░░░░░░►
+    [Theory]
+    // ◄█►
+    [InlineData (3, 3, -1, 0)]
+    [InlineData (3, 3, 0, 0)]
+    // 012
+    // ---
+    // ◄█►
+    [InlineData (3, 3, 1, 0)]
+    [InlineData (3, 3, 2, 0)]
+
+    // ◄██►
+    [InlineData (4, 2, 1, 0)]
+    [InlineData (4, 2, 2, 0)]
+
+    // 0123
+    //  ---
+    // ◄██►
+    [InlineData (4, 3, 0, 0)] // scrollBarWidth/VisibleContentSize > size - scrolling doesn't make sense. Size should clamp to scrollSlider.Size.
+    // ◄██►
+    [InlineData (4, 3, 1, 0)]
+    // ◄██►
+    [InlineData (4, 3, 2, 0)]
+
+
+    // 01234
+    //  ----
+    // ◄██►
+    [InlineData (4, 4, 0, 0)] // scrollBarWidth/VisibleContentSize == size - scrolling doesn't make sense. Size should clamp to scrollSlider.Size.
+    // ◄██►
+    [InlineData (4, 4, 1, 0)]
+    // ◄██►
+    [InlineData (4, 4, 2, 0)]
+
+    // 012345
+    // ◄███►
+    //  -----
+    [InlineData (5, 5, 3, 0)]
+    [InlineData (5, 5, 4, 0)]
+
+    // 0123456
+    // ◄██░►
+    //  ^-----
+    [InlineData (5, 6, 0, 0)]
+    // ◄░██►
+    //  -^----
+    [InlineData (5, 6, 1, 1)]
+    [InlineData (5, 6, 2, 1)]
+
+    // 012346789
+    // ◄█░░►
+    //  ^--------
+    [InlineData (5, 10, -1, 0)]
+    [InlineData (5, 10, 0, 0)]
+
+    // 0123456789
+    // ◄░█░►
+    //  --^-------
+    [InlineData (5, 10, 1, 3)]
+
+    // ◄░░█►
+    //  ----^----
+    [InlineData (5, 10, 2, 5)]
+
+    // ◄░░█►
+    //  ------^---
+    [InlineData (5, 10, 4, 5)]
+
+    // ◄░████░░░►
+    //  --------------------
+    [InlineData (10, 20, 0, 0)]
+
+    public void CalculatePosition_Calculates (int visibleContentSize, int scrollableContentSize, int sliderPosition, int expectedContentPosition)
+    {
+        // Arrange
+        var scrollBar = new ScrollBar
+        {
+            VisibleContentSize = visibleContentSize,
+            ScrollableContentSize = scrollableContentSize,
+            Orientation = Orientation.Horizontal // Use Horizontal because it's easier to visualize
+        };
+        scrollBar.Frame = new (0, 0, visibleContentSize, 0);
+
+        // Act
+        var contentPosition = scrollBar.CalculatePosition (sliderPosition);
+
+        // Assert
+        Assert.Equal (expectedContentPosition, contentPosition);
+    }
     [Fact]
-    public void ContentPosition_Event_Cancelables ()
+    public void Position_Event_Cancelables ()
     {
         var changingCount = 0;
         var changedCount = 0;
         var scrollBar = new ScrollBar { };
-        scrollBar.Size = 4;
+        scrollBar.ScrollableContentSize = 5;
         scrollBar.Frame = new Rectangle (0, 0, 1, 4); // Needs to be at least 4 for slider to move
 
-        scrollBar.ContentPositionChanging += (s, e) =>
+        scrollBar.PositionChanging += (s, e) =>
                                             {
                                                 if (changingCount == 0)
                                                 {
@@ -109,79 +598,49 @@ public class ScrollBarTests
 
                                                 changingCount++;
                                             };
-        scrollBar.ContentPositionChanged += (s, e) => changedCount++;
+        scrollBar.PositionChanged += (s, e) => changedCount++;
 
-        scrollBar.ContentPosition = 1;
-        Assert.Equal (0, scrollBar.ContentPosition);
+        scrollBar.Position = 1;
+        Assert.Equal (0, scrollBar.Position);
         Assert.Equal (1, changingCount);
         Assert.Equal (0, changedCount);
 
-        scrollBar.ContentPosition = 1;
-        Assert.Equal (1, scrollBar.ContentPosition);
+        scrollBar.Position = 1;
+        Assert.Equal (1, scrollBar.Position);
         Assert.Equal (2, changingCount);
         Assert.Equal (1, changedCount);
     }
-
-
-
-    [Fact (Skip = "Disabled - Will put this feature in View")]
-    [AutoInitShutdown]
-    public void KeepContentInAllViewport_True_False ()
-    {
-        var view = new View { Width = Dim.Fill (), Height = Dim.Fill () };
-        view.Padding.Thickness = new (0, 0, 2, 0);
-        view.SetContentSize (new (view.Viewport.Width, 30));
-        var scrollBar = new ScrollBar { Width = 2, Height = Dim.Fill (), Size = view.GetContentSize ().Height };
-        scrollBar.SliderPositionChanged += (_, e) => view.Viewport = view.Viewport with { Y = e.CurrentValue };
-        view.Padding.Add (scrollBar);
-        var top = new Toplevel ();
-        top.Add (view);
-        Application.Begin (top);
-
-        Assert.False (scrollBar.KeepContentInAllViewport);
-        scrollBar.KeepContentInAllViewport = true;
-        Assert.Equal (80, view.Padding.Viewport.Width);
-        Assert.Equal (25, view.Padding.Viewport.Height);
-        Assert.Equal (2, scrollBar.Viewport.Width);
-        Assert.Equal (25, scrollBar.Viewport.Height);
-        Assert.Equal (30, scrollBar.Size);
-
-        scrollBar.KeepContentInAllViewport = false;
-        scrollBar.ContentPosition = 50;
-        Assert.Equal (scrollBar.GetSliderPosition (), scrollBar.Size - 1);
-        Assert.Equal (scrollBar.GetSliderPosition (), view.Viewport.Y);
-        Assert.Equal (29, scrollBar.GetSliderPosition ());
-        Assert.Equal (29, view.Viewport.Y);
-
-        top.Dispose ();
-    }
+    #endregion Position
 
 
     [Fact]
-    public void Size_Cannot_Be_Negative ()
+    public void ScrollableContentSize_Cannot_Be_Negative ()
     {
-        var scrollBar = new ScrollBar { Height = 10, Size = -1 };
-        Assert.Equal (0, scrollBar.Size);
-        scrollBar.Size = -10;
-        Assert.Equal (0, scrollBar.Size);
+        var scrollBar = new ScrollBar { Height = 10, ScrollableContentSize = -1 };
+        Assert.Equal (0, scrollBar.ScrollableContentSize);
+        scrollBar.ScrollableContentSize = -10;
+        Assert.Equal (0, scrollBar.ScrollableContentSize);
     }
 
     [Fact]
-    public void SizeChanged_Event ()
+    public void ScrollableContentSizeChanged_Event ()
     {
         var count = 0;
         var scrollBar = new ScrollBar ();
-        scrollBar.SizeChanged += (s, e) => count++;
+        scrollBar.ScrollableContentSizeChanged += (s, e) => count++;
 
-        scrollBar.Size = 10;
-        Assert.Equal (10, scrollBar.Size);
+        scrollBar.ScrollableContentSize = 10;
+        Assert.Equal (10, scrollBar.ScrollableContentSize);
         Assert.Equal (1, count);
     }
 
     [Theory]
     [SetupFakeDriver]
 
-    #region Vertical
+    #region Draw
+
+
+    #region Horizontal
 
     #region Super 10 - ScrollBar 8
     [InlineData (
@@ -235,7 +694,7 @@ public class ScrollBarTests
                     Orientation.Horizontal,
                     @"
 ┌──────────┐
-│◄████░░░►│
+│◄████░░░►│
 └──────────┘
 ")]
 
@@ -553,9 +1012,9 @@ public class ScrollBarTests
 │ ░████░░░ │
 └──────────┘
 ")]
-    #endregion Vertical
+    #endregion Horizontal
 
-    #region Horizontal
+    #region Vertical
 
     [InlineData (
                     1,
@@ -659,16 +1118,16 @@ public class ScrollBarTests
 │ ▼ │
 └───┘
 ")]
-    #endregion
+    #endregion Vertical
 
 
-    public void Draws_Correctly (int superWidth, int superHeight, int contentSize, int contentPosition, Orientation orientation, string expected)
+    public void Draws_Correctly (int width, int height, int contentSize, int contentPosition, Orientation orientation, string expected)
     {
         var super = new Window
         {
             Id = "super",
-            Width = superWidth + 2,
-            Height = superHeight + 2
+            Width = width + 2,
+            Height = height + 2
         };
 
         var scrollBar = new ScrollBar
@@ -678,38 +1137,32 @@ public class ScrollBarTests
 
         if (orientation == Orientation.Vertical)
         {
-            scrollBar.Width = Dim.Fill ();
+            scrollBar.Width = 1;
+            scrollBar.Height = height;
         }
         else
         {
-            scrollBar.Height = Dim.Fill ();
+            scrollBar.Width = width;
+            scrollBar.Height = 1;
         }
         super.Add (scrollBar);
 
-        scrollBar.Size = contentSize;
-        scrollBar.ContentPosition = contentPosition;
+        scrollBar.ScrollableContentSize = contentSize;
+        scrollBar.Position = contentPosition;
+
+        int sliderPos = scrollBar.CalculateSliderPosition (contentPosition, NavigationDirection.Forward);
 
         super.BeginInit ();
         super.EndInit ();
         super.Layout ();
         super.Draw ();
 
-        _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
     }
+    #endregion Draw
 
-    private View ScrollBarSuperView ()
-    {
-        var view = new View
-        {
-            Width = Dim.Fill (),
-            Height = Dim.Fill ()
-        };
-
-        var top = new Toplevel ();
-        top.Add (view);
+    #region Mouse
 
-        return view;
-    }
 
 
     [Theory]
@@ -727,7 +1180,7 @@ public class ScrollBarTests
         {
             Id = "scrollBar",
             Orientation = orientation,
-            Size = 20,
+            ScrollableContentSize = 20,
             Increment = increment
         };
 
@@ -735,13 +1188,13 @@ public class ScrollBarTests
         RunState rs = Application.Begin (top);
 
         // Scroll to end
-        scrollBar.ContentPosition = 20;
-        Assert.Equal (10, scrollBar.ContentPosition);
+        scrollBar.Position = 20;
+        Assert.Equal (10, scrollBar.Position);
         Application.RunIteration (ref rs);
 
         Assert.Equal (4, scrollBar.GetSliderPosition ());
-        Assert.Equal (10, scrollBar.ContentPosition);
-        int initialPos = scrollBar.ContentPosition;
+        Assert.Equal (10, scrollBar.Position);
+        int initialPos = scrollBar.Position;
 
         Application.RaiseMouseEvent (new ()
         {
@@ -750,7 +1203,7 @@ public class ScrollBarTests
         });
         Application.RunIteration (ref rs);
 
-        Assert.Equal (initialPos - increment, scrollBar.ContentPosition);
+        Assert.Equal (initialPos - increment, scrollBar.Position);
 
         Application.ResetState (true);
     }
@@ -771,7 +1224,7 @@ public class ScrollBarTests
         {
             Id = "scrollBar",
             Orientation = orientation,
-            Size = 20,
+            ScrollableContentSize = 20,
             Increment = increment
         };
 
@@ -779,12 +1232,12 @@ public class ScrollBarTests
         RunState rs = Application.Begin (top);
 
         // Scroll to top
-        scrollBar.ContentPosition = 0;
+        scrollBar.Position = 0;
         Application.RunIteration (ref rs);
 
         Assert.Equal (0, scrollBar.GetSliderPosition ());
-        Assert.Equal (0, scrollBar.ContentPosition);
-        int initialPos = scrollBar.ContentPosition;
+        Assert.Equal (0, scrollBar.Position);
+        int initialPos = scrollBar.Position;
 
         Application.RaiseMouseEvent (new ()
         {
@@ -793,136 +1246,44 @@ public class ScrollBarTests
         });
         Application.RunIteration (ref rs);
 
-        Assert.Equal (initialPos + increment, scrollBar.ContentPosition);
+        Assert.Equal (initialPos + increment, scrollBar.Position);
 
         Application.ResetState (true);
     }
+    #endregion Mouse
 
-    [Theory]
-    [InlineData (-1, 10, 1)]
-    [InlineData (0, 10, 1)]
-    [InlineData (10, 15, 5)]
-    [InlineData (10, 5, 10)]
-    [InlineData (10, 3, 10)]
-    [InlineData (10, 2, 10)]
-    [InlineData (10, 1, 10)]
-    [InlineData (10, 0, 1)]
-    [InlineData (10, 10, 8)]
-    [InlineData (10, 20, 4)]
-    [InlineData (10, 100, 1)]
-    [InlineData (15, 10, 15)]
-    [InlineData (15, 0, 1)]
-    [InlineData (15, 1, 15)]
-    [InlineData (15, 2, 15)]
-    [InlineData (15, 3, 15)]
-    [InlineData (15, 5, 15)]
-    [InlineData (15, 14, 13)]
-    [InlineData (15, 15, 13)]
-    [InlineData (15, 16, 12)]
-    [InlineData (20, 10, 20)]
-    [InlineData (100, 10, 100)]
-    public void CalculateSliderSize_Width_Matches_ViewportDimension (int viewportDimension, int size, int expectedSliderSize)
-    {
-        // Arrange
-        var scrollBar = new ScrollBar
-        {
-            ViewportDimension = viewportDimension,
-            Size = size,
-            Orientation = Orientation.Horizontal // Assuming horizontal for simplicity
-        };
-        scrollBar.Width = viewportDimension; // Changing orientation changes Width
-        scrollBar.BeginInit ();
-        scrollBar.EndInit ();
-        scrollBar.Layout ();
-
-        // Act
-        var sliderSize = scrollBar.CalculateSliderSize ();
-
-
-        // Assert
-        Assert.Equal (expectedSliderSize, sliderSize);
-    }
-
-    // 012345678901
-    // ◄█░░░░░░░░░►
-    [Theory]
-    // ◄█►
-    [InlineData (3, 3, -1, 0)]
-    [InlineData (3, 3, 0, 0)]
-    [InlineData (3, 3, 1, 0)]
-    [InlineData (3, 3, 2, 0)]
-
-    // ◄██►
-    [InlineData (4, 2, 1, 0)]
-    [InlineData (4, 2, 2, 0)]
-
-    // 0123
-    //  ---
-    // ◄█░►
-    [InlineData (4, 3, 0, 0)]
-    // ◄░█►
-    [InlineData (4, 3, 1, 1)]
-    // ◄░█►
-    [InlineData (4, 3, 2, 1)]
-
-
-    // 01234
-    //  ----
-    // ◄█░►
-    [InlineData (4, 4, 0, 0)]
-    // ◄░█►
-    [InlineData (4, 4, 1, 1)]
-    // ◄░█►
-    [InlineData (4, 4, 2, 1)]
-
-    // 012345
-    // ◄███►
-    //  -----
-    [InlineData (5, 5, 3, 0)]
-    [InlineData (5, 5, 4, 0)]
-
-    // 0123456
-    // ◄██░►
-    //  ------
-    [InlineData (5, 6, 0, 0)]
-    [InlineData (5, 6, 1, 1)]
-    [InlineData (5, 6, 2, 1)]
 
-    // 01234567890
-    // ◄█░░░►
-    //  ----------
-    [InlineData (5, 10, -1, 0)]
-    [InlineData (5, 10, 0, 0)]
 
-    // 01234567890
-    // ◄░█░░►
-    //  --^-------
-    [InlineData (5, 10, 1, 2)]
-    [InlineData (5, 10, 2, 3)]
-    [InlineData (5, 10, 3, 3)]
-    [InlineData (5, 10, 4, 3)]
-    [InlineData (5, 10, 5, 3)]
-    [InlineData (5, 10, 6, 3)]
-    [InlineData (5, 10, 7, 3)]
-    [InlineData (5, 10, 8, 3)]
-    [InlineData (5, 10, 9, 3)]
-    [InlineData (5, 10, 10, 3)]
-    public void CalculateContentPosition_ComprehensiveTests (int viewportDimension, int size, int sliderPosition, int expectedContentPosition)
+    [Fact (Skip = "Disabled - Will put this feature in View")]
+    [AutoInitShutdown]
+    public void KeepContentInAllViewport_True_False ()
     {
-        // Arrange
-        var scrollBar = new ScrollBar
-        {
-            ViewportDimension = viewportDimension,
-            Size = size,
-            Orientation = Orientation.Horizontal // Assuming horizontal for simplicity
-        };
-        scrollBar.Width = viewportDimension; // Changing orientation changes Width
-        scrollBar.Layout ();
+        var view = new View { Width = Dim.Fill (), Height = Dim.Fill () };
+        view.Padding.Thickness = new (0, 0, 2, 0);
+        view.SetContentSize (new (view.Viewport.Width, 30));
+        var scrollBar = new ScrollBar { Width = 2, Height = Dim.Fill (), ScrollableContentSize = view.GetContentSize ().Height };
+        scrollBar.SliderPositionChanged += (_, e) => view.Viewport = view.Viewport with { Y = e.CurrentValue };
+        view.Padding.Add (scrollBar);
+        var top = new Toplevel ();
+        top.Add (view);
+        Application.Begin (top);
 
-        // Act
-        var contentPosition = scrollBar.CalculateContentPosition (sliderPosition);
+        Assert.False (scrollBar.KeepContentInAllViewport);
+        scrollBar.KeepContentInAllViewport = true;
+        Assert.Equal (80, view.Padding.Viewport.Width);
+        Assert.Equal (25, view.Padding.Viewport.Height);
+        Assert.Equal (2, scrollBar.Viewport.Width);
+        Assert.Equal (25, scrollBar.Viewport.Height);
+        Assert.Equal (30, scrollBar.ScrollableContentSize);
 
-        // Assert
-        Assert.Equal (expectedContentPosition, contentPosition);
+        scrollBar.KeepContentInAllViewport = false;
+        scrollBar.Position = 50;
+        Assert.Equal (scrollBar.GetSliderPosition (), scrollBar.ScrollableContentSize - 1);
+        Assert.Equal (scrollBar.GetSliderPosition (), view.Viewport.Y);
+        Assert.Equal (29, scrollBar.GetSliderPosition ());
+        Assert.Equal (29, view.Viewport.Y);
+
+        top.Dispose ();
     }
+
 }

+ 27 - 27
UnitTests/Views/ScrollSliderTests.cs

@@ -21,7 +21,7 @@ public class ScrollSliderTests (ITestOutputHelper output)
         Assert.Equal (0, scrollSlider.Frame.X);
         Assert.Equal (0, scrollSlider.Frame.Y);
         Assert.Equal (1, scrollSlider.Size);
-        Assert.Equal (2048, scrollSlider.ViewportDimension);
+        Assert.Equal (2048, scrollSlider.VisibleContentSize);
     }
 
     [Fact]
@@ -46,7 +46,7 @@ public class ScrollSliderTests (ITestOutputHelper output)
         Assert.Equal (0, scrollSlider.Frame.X);
         Assert.Equal (0, scrollSlider.Frame.Y);
         Assert.Equal (1, scrollSlider.Size);
-        Assert.Equal (10, scrollSlider.ViewportDimension);
+        Assert.Equal (10, scrollSlider.VisibleContentSize);
     }
 
     //[Fact]
@@ -116,12 +116,12 @@ public class ScrollSliderTests (ITestOutputHelper output)
 
     [Theory]
     [CombinatorialData]
-    public void Size_Clamps_To_ViewportDimensions ([CombinatorialRange (1, 6, 1)] int dimension, [CombinatorialRange (-1, 6, 1)] int sliderSize, Orientation orientation)
+    public void Size_Clamps_To_VisibleContentSizes ([CombinatorialRange (1, 6, 1)] int dimension, [CombinatorialRange (-1, 6, 1)] int sliderSize, Orientation orientation)
     {
         var scrollSlider = new ScrollSlider
         {
             Orientation = orientation,
-            ViewportDimension = dimension,
+            VisibleContentSize = dimension,
             Size = sliderSize,
         };
         scrollSlider.Layout ();
@@ -132,7 +132,7 @@ public class ScrollSliderTests (ITestOutputHelper output)
     }
 
     [Fact]
-    public void ViewportDimension_Not_Set_Uses_SuperView ()
+    public void VisibleContentSize_Not_Set_Uses_SuperView ()
     {
         View super = new ()
         {
@@ -146,11 +146,11 @@ public class ScrollSliderTests (ITestOutputHelper output)
 
         super.Add (scrollSlider);
         super.Layout ();
-        Assert.Equal (5, scrollSlider.ViewportDimension);
+        Assert.Equal (5, scrollSlider.VisibleContentSize);
     }
 
     [Fact]
-    public void ViewportDimension_Set_Overrides_SuperView ()
+    public void VisibleContentSize_Set_Overrides_SuperView ()
     {
         View super = new ()
         {
@@ -160,34 +160,34 @@ public class ScrollSliderTests (ITestOutputHelper output)
         };
         var scrollSlider = new ScrollSlider
         {
-            ViewportDimension = 10,
+            VisibleContentSize = 10,
         };
 
         super.Add (scrollSlider);
         super.Layout ();
-        Assert.Equal (10, scrollSlider.ViewportDimension);
+        Assert.Equal (10, scrollSlider.VisibleContentSize);
 
         super.Height = 3;
         super.Layout ();
-        Assert.Equal (10, scrollSlider.ViewportDimension);
+        Assert.Equal (10, scrollSlider.VisibleContentSize);
 
         super.Height = 7;
         super.Layout ();
-        Assert.Equal (10, scrollSlider.ViewportDimension);
+        Assert.Equal (10, scrollSlider.VisibleContentSize);
 
     }
 
     [Theory]
     [CombinatorialData]
-    public void ViewportDimensions_Clamps_0_To_Dimension ([CombinatorialRange (0, 10, 1)] int dimension, Orientation orientation)
+    public void VisibleContentSizes_Clamps_0_To_Dimension ([CombinatorialRange (0, 10, 1)] int dimension, Orientation orientation)
     {
         var scrollSlider = new ScrollSlider
         {
             Orientation = orientation,
-            ViewportDimension = dimension,
+            VisibleContentSize = dimension,
         };
 
-        Assert.InRange (scrollSlider.ViewportDimension, 1, 10);
+        Assert.InRange (scrollSlider.VisibleContentSize, 1, 10);
 
         View super = new ()
         {
@@ -203,16 +203,16 @@ public class ScrollSliderTests (ITestOutputHelper output)
         super.Add (scrollSlider);
         super.Layout ();
 
-        Assert.InRange (scrollSlider.ViewportDimension, 1, 10);
+        Assert.InRange (scrollSlider.VisibleContentSize, 1, 10);
 
-        scrollSlider.ViewportDimension = dimension;
+        scrollSlider.VisibleContentSize = dimension;
 
-        Assert.InRange (scrollSlider.ViewportDimension, 1, 10);
+        Assert.InRange (scrollSlider.VisibleContentSize, 1, 10);
     }
 
     [Theory]
     [CombinatorialData]
-    public void ClampPosition_WithSuperView_Clamps_To_ViewPort_Minus_Size_If_ViewportDimension_Not_Set ([CombinatorialRange (10, 10, 1)] int dimension, [CombinatorialRange (1, 5, 1)] int sliderSize, [CombinatorialRange (-1, 10, 2)] int sliderPosition, Orientation orientation)
+    public void ClampPosition_WithSuperView_Clamps_To_ViewPort_Minus_Size_If_VisibleContentSize_Not_Set ([CombinatorialRange (10, 10, 1)] int dimension, [CombinatorialRange (1, 5, 1)] int sliderSize, [CombinatorialRange (-1, 10, 2)] int sliderPosition, Orientation orientation)
     {
         View super = new ()
         {
@@ -228,7 +228,7 @@ public class ScrollSliderTests (ITestOutputHelper output)
         super.Add (scrollSlider);
         super.Layout ();
 
-        Assert.Equal(dimension, scrollSlider.ViewportDimension);
+        Assert.Equal(dimension, scrollSlider.VisibleContentSize);
 
         int clampedPosition = scrollSlider.ClampPosition (sliderPosition);
 
@@ -237,7 +237,7 @@ public class ScrollSliderTests (ITestOutputHelper output)
 
     [Theory]
     [CombinatorialData]
-    public void ClampPosition_WithSuperView_Clamps_To_ViewportDimension_Minus_Size ([CombinatorialRange (10, 10, 1)] int dimension, [CombinatorialRange (1, 5, 1)] int sliderSize, [CombinatorialRange (-1, 10, 2)] int sliderPosition, Orientation orientation)
+    public void ClampPosition_WithSuperView_Clamps_To_VisibleContentSize_Minus_Size ([CombinatorialRange (10, 10, 1)] int dimension, [CombinatorialRange (1, 5, 1)] int sliderSize, [CombinatorialRange (-1, 10, 2)] int sliderPosition, Orientation orientation)
     {
         View super = new ()
         {
@@ -248,7 +248,7 @@ public class ScrollSliderTests (ITestOutputHelper output)
         var scrollSlider = new ScrollSlider
         {
             Orientation = orientation,
-            ViewportDimension = dimension,
+            VisibleContentSize = dimension,
             Size = sliderSize,
         };
         super.Add (scrollSlider);
@@ -261,12 +261,12 @@ public class ScrollSliderTests (ITestOutputHelper output)
 
     [Theory]
     [CombinatorialData]
-    public void ClampPosition_NoSuperView_Clamps_To_ViewportDimension_Minus_Size ([CombinatorialRange (10, 10, 1)] int dimension, [CombinatorialRange (1, 5, 1)] int sliderSize, [CombinatorialRange (-1, 10, 2)] int sliderPosition, Orientation orientation)
+    public void ClampPosition_NoSuperView_Clamps_To_VisibleContentSize_Minus_Size ([CombinatorialRange (10, 10, 1)] int dimension, [CombinatorialRange (1, 5, 1)] int sliderSize, [CombinatorialRange (-1, 10, 2)] int sliderPosition, Orientation orientation)
     {
         var scrollSlider = new ScrollSlider
         {
             Orientation = orientation,
-            ViewportDimension = dimension,
+            VisibleContentSize = dimension,
             Size = sliderSize,
         };
 
@@ -277,12 +277,12 @@ public class ScrollSliderTests (ITestOutputHelper output)
 
     [Theory]
     [CombinatorialData]
-    public void Position_Clamps_To_ViewportDimension ([CombinatorialRange (0, 5, 1)] int dimension, [CombinatorialRange(1, 5, 1)] int sliderSize, [CombinatorialRange (-1, 10, 2)] int sliderPosition, Orientation orientation)
+    public void Position_Clamps_To_VisibleContentSize ([CombinatorialRange (0, 5, 1)] int dimension, [CombinatorialRange(1, 5, 1)] int sliderSize, [CombinatorialRange (-1, 10, 2)] int sliderPosition, Orientation orientation)
     {
         var scrollSlider = new ScrollSlider
         {
             Orientation = orientation,
-            ViewportDimension = dimension,
+            VisibleContentSize = dimension,
             Size = sliderSize,
             Position = sliderPosition
         };
@@ -318,7 +318,7 @@ public class ScrollSliderTests (ITestOutputHelper output)
 
     [Theory]
     [CombinatorialData]
-    public void Position_Clamps_To_ViewportDimension_With_SuperView ([CombinatorialRange (0, 5, 1)] int dimension, [CombinatorialRange (1, 5, 1)] int sliderSize, [CombinatorialRange (-2, 10, 2)] int sliderPosition, Orientation orientation)
+    public void Position_Clamps_To_VisibleContentSize_With_SuperView ([CombinatorialRange (0, 5, 1)] int dimension, [CombinatorialRange (1, 5, 1)] int sliderSize, [CombinatorialRange (-2, 10, 2)] int sliderPosition, Orientation orientation)
     {
         var super = new View
         {
@@ -330,7 +330,7 @@ public class ScrollSliderTests (ITestOutputHelper output)
         var scrollSlider = new ScrollSlider
         {
             Orientation = orientation,
-            ViewportDimension = dimension,
+            VisibleContentSize = dimension,
             Size = sliderSize,
             Position = sliderPosition
         };