Browse Source

Add KeepContentInAllViewport to Scroll.

BDisp 11 months ago
parent
commit
f54ded3351

+ 48 - 4
Terminal.Gui/Views/Scroll/Scroll.cs

@@ -33,6 +33,7 @@ public class Scroll : View
     private Orientation _orientation;
     private int _position;
     private int _size;
+    private bool _keepContentInAllViewport = true;
 
     /// <inheritdoc/>
     public override void EndInit ()
@@ -42,6 +43,37 @@ public class Scroll : View
         AdjustScroll ();
     }
 
+    /// <summary>Get or sets if the view-port is kept in all visible area of this <see cref="Scroll"/></summary>
+    public bool KeepContentInAllViewport
+    {
+        get => _keepContentInAllViewport;
+        set
+        {
+            if (_keepContentInAllViewport != value)
+            {
+                _keepContentInAllViewport = value;
+                var pos = 0;
+
+                if (value && Orientation == Orientation.Horizontal && _position + SuperViewAsScrollBar!.Viewport.Width > Size)
+                {
+                    pos = Size - SuperViewAsScrollBar.Viewport.Width;
+                }
+
+                if (value && Orientation == Orientation.Vertical && _position + SuperViewAsScrollBar!.Viewport.Height > Size)
+                {
+                    pos = _size - SuperViewAsScrollBar.Viewport.Height;
+                }
+
+                if (pos != 0)
+                {
+                    Position = pos;
+                    SetNeedsDisplay ();
+                    AdjustScroll ();
+                }
+            }
+        }
+    }
+
     /// <summary>
     ///     Gets or sets if the Scroll is oriented vertically or horizontally.
     /// </summary>
@@ -74,21 +106,21 @@ public class Scroll : View
                 SetRelativeLayout (SuperViewAsScrollBar.Frame.Size);
             }
 
-            int barSize = BarSize;
+            int pos = SetPosition (value);
 
-            if (value + barSize > Size)
+            if (pos == _position)
             {
                 return;
             }
 
-            CancelEventArgs<int> args = OnPositionChanging (_position, value);
+            CancelEventArgs<int> args = OnPositionChanging (_position, pos);
 
             if (args.Cancel)
             {
                 return;
             }
 
-            _position = value;
+            _position = pos;
 
             AdjustScroll ();
 
@@ -205,6 +237,18 @@ public class Scroll : View
 
     private int BarSize => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
 
+    private int SetPosition (int position)
+    {
+        int barSize = BarSize;
+
+        if (position + barSize > Size)
+        {
+            return KeepContentInAllViewport ? Math.Max (Size - barSize, 0) : Math.Max (Size - 1, 0);
+        }
+
+        return position;
+    }
+
     private void SetScrollText ()
     {
         TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;

+ 11 - 1
Terminal.Gui/Views/Scroll/ScrollBar.cs

@@ -38,7 +38,10 @@ public class ScrollBar : View
     private bool _autoHide = true;
     private bool _showScrollIndicator = 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"/>.</summary>
+    /// <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"/>.
+    /// </summary>
     public bool AutoHide
     {
         get => _autoHide;
@@ -52,6 +55,13 @@ public class ScrollBar : View
         }
     }
 
+    /// <summary>Get or sets if the view-port is kept in all visible area of this <see cref="ScrollBar"/></summary>
+    public bool KeepContentInAllViewport
+    {
+        get => _scroll.KeepContentInAllViewport;
+        set => _scroll.KeepContentInAllViewport = value;
+    }
+
     /// <summary>Gets or sets if a scrollbar is vertical or horizontal.</summary>
     public Orientation Orientation
     {

+ 14 - 5
Terminal.Gui/Views/Scroll/ScrollSlider.cs

@@ -28,8 +28,12 @@ internal class ScrollSlider : View
 
         SetContentSize (
                         new (
-                             SuperViewAsScroll.Orientation == Orientation.Vertical ? SuperViewAsScroll.GetContentSize ().Width : sliderLocationAndDimension.Dimension,
-                             SuperViewAsScroll.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Dimension : SuperViewAsScroll.GetContentSize ().Height
+                             SuperViewAsScroll.Orientation == Orientation.Vertical
+                                 ? SuperViewAsScroll.GetContentSize ().Width
+                                 : sliderLocationAndDimension.Dimension,
+                             SuperViewAsScroll.Orientation == Orientation.Vertical
+                                 ? sliderLocationAndDimension.Dimension
+                                 : SuperViewAsScroll.GetContentSize ().Height
                             ));
         SetSliderText ();
     }
@@ -144,7 +148,9 @@ internal class ScrollSlider : View
             return 0;
         }
 
-        int scrollSize = SuperViewAsScroll.Orientation == Orientation.Vertical ? SuperViewAsScroll.GetContentSize ().Height : SuperViewAsScroll.GetContentSize ().Width;
+        int scrollSize = SuperViewAsScroll.Orientation == Orientation.Vertical
+                             ? SuperViewAsScroll.GetContentSize ().Height
+                             : SuperViewAsScroll.GetContentSize ().Width;
 
         // Ensure the Position is valid if the slider is at end
         // We use Frame here instead of ContentSize because even if the slider has a margin or border, Frame indicates the actual size
@@ -164,7 +170,9 @@ internal class ScrollSlider : View
             return new (0, 0);
         }
 
-        int scrollSize = SuperViewAsScroll.Orientation == Orientation.Vertical ? SuperViewAsScroll.GetContentSize ().Height : SuperViewAsScroll.GetContentSize ().Width;
+        int scrollSize = SuperViewAsScroll.Orientation == Orientation.Vertical
+                             ? SuperViewAsScroll.GetContentSize ().Height
+                             : SuperViewAsScroll.GetContentSize ().Width;
         int location;
         int dimension;
 
@@ -173,7 +181,8 @@ internal class ScrollSlider : View
             dimension = (int)Math.Min (Math.Max (Math.Ceiling ((double)scrollSize * scrollSize / SuperViewAsScroll.Size), 1), scrollSize);
 
             // Ensure the Position is valid
-            if (SuperViewAsScroll.Position > 0 && SuperViewAsScroll.Position + scrollSize > SuperViewAsScroll.Size)
+            if (SuperViewAsScroll.Position > 0
+                && SuperViewAsScroll.Position + scrollSize > SuperViewAsScroll.Size + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize))
             {
                 SuperViewAsScroll.Position = SuperViewAsScroll.Size - scrollSize;
             }

+ 37 - 6
UnitTests/Views/ScrollBarTests.cs

@@ -231,6 +231,37 @@ public class ScrollBarTests
         Assert.True (scrollBar.AutoHide);
     }
 
+    [Fact]
+    [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.PositionChanged += (_, e) => view.Viewport = view.Viewport with { Y = e.CurrentValue };
+        view.Padding.Add (scrollBar);
+        var top = new Toplevel ();
+        top.Add (view);
+        Application.Begin (top);
+
+        Assert.True (scrollBar.KeepContentInAllViewport);
+        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.Position = 50;
+        Assert.Equal (scrollBar.Position, scrollBar.Size - 1);
+        Assert.Equal (scrollBar.Position, view.Viewport.Y);
+        Assert.Equal (29, scrollBar.Position);
+        Assert.Equal (29, view.Viewport.Y);
+
+        top.Dispose ();
+    }
+
     [Theory]
     [AutoInitShutdown]
     [InlineData (
@@ -848,9 +879,9 @@ public class ScrollBarTests
     }
 
     [Theory]
-    [InlineData (Orientation.Vertical, 20, 10)]
-    [InlineData (Orientation.Vertical, 40, 30)]
-    public void Position_Cannot_Be_Negative_Nor_Greater_Than_Size_Minus_Frame_Length (Orientation orientation, int size, int expectedPos)
+    [InlineData (Orientation.Vertical, 20, 12, 10)]
+    [InlineData (Orientation.Vertical, 40, 32, 30)]
+    public void Position_Cannot_Be_Negative_Nor_Greater_Than_Size_Minus_Frame_Length (Orientation orientation, int size, int expectedPos1, int expectedPos2)
     {
         var scrollBar = new ScrollBar { Orientation = orientation, Height = 10, Size = size };
         Assert.Equal (0, scrollBar.Position);
@@ -859,10 +890,10 @@ public class ScrollBarTests
         Assert.Equal (0, scrollBar.Position);
 
         scrollBar.Position = size;
-        Assert.Equal (0, scrollBar.Position);
+        Assert.Equal (expectedPos1, scrollBar.Position);
 
-        scrollBar.Position = expectedPos;
-        Assert.Equal (expectedPos, scrollBar.Position);
+        scrollBar.Position = expectedPos2;
+        Assert.Equal (expectedPos2, scrollBar.Position);
     }
 
     [Fact]

+ 33 - 1
UnitTests/Views/ScrollTests.cs

@@ -190,6 +190,38 @@ public class ScrollTests
         Assert.Equal (Orientation.Vertical, scroll.Orientation);
         Assert.Equal (0, scroll.Size);
         Assert.Equal (0, scroll.Position);
+        Assert.True (scroll.KeepContentInAllViewport);
+    }
+
+    [Fact]
+    [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 scroll = new Scroll { Width = 2, Height = Dim.Fill (), Size = view.GetContentSize ().Height };
+        scroll.PositionChanged += (_, e) => view.Viewport = view.Viewport with { Y = e.CurrentValue };
+        view.Padding.Add (scroll);
+        var top = new Toplevel ();
+        top.Add (view);
+        Application.Begin (top);
+
+        Assert.True (scroll.KeepContentInAllViewport);
+        Assert.Equal (80, view.Padding.Viewport.Width);
+        Assert.Equal (25, view.Padding.Viewport.Height);
+        Assert.Equal (2, scroll.Viewport.Width);
+        Assert.Equal (25, scroll.Viewport.Height);
+        Assert.Equal (30, scroll.Size);
+
+        scroll.KeepContentInAllViewport = false;
+        scroll.Position = 50;
+        Assert.Equal (scroll.Position, scroll.Size - 1);
+        Assert.Equal (scroll.Position, view.Viewport.Y);
+        Assert.Equal (29, scroll.Position);
+        Assert.Equal (29, view.Viewport.Y);
+
+        top.Dispose ();
     }
 
     [Theory]
@@ -759,7 +791,7 @@ public class ScrollTests
         Assert.Equal (0, scroll.Position);
 
         scroll.Position = size;
-        Assert.Equal (0, scroll.Position);
+        Assert.Equal (expectedPos, scroll.Position);
 
         scroll.Position = expectedPos;
         Assert.Equal (expectedPos, scroll.Position);