diff --git a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs index abfd13c75..d4e149e47 100644 --- a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs +++ b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs @@ -6,15 +6,15 @@ using System.Drawing; namespace Terminal.Gui; /// -/// Indicates the size of scrollable content and provides a visible element, referred to as the "ScrollSlider" that -/// that is sized to -/// show the proportion of the scrollable content to the size of the . The ScrollSlider -/// can be dragged with the mouse. A Scroll can be oriented either vertically or horizontally and is used within a -/// . +/// Indicates the size of scrollable content and controls the position of the visible content, either vertically or horizontally. +/// Two s are provided, one to scroll up or left and one to scroll down or right. Between the buttons is a that can be dragged to +/// control the position of the visible content. The ScrollSlier is sized to show the proportion of the scrollable content to the size of the . /// /// /// -/// By default, this view cannot be focused and does not support keyboard. +/// +/// +/// By default, this view cannot be focused and does not support keyboard input. /// /// public class ScrollBar : View, IOrientation, IDesignable @@ -58,8 +58,6 @@ public class ScrollBar : View, IOrientation, IDesignable _orientationHelper = new (this); // Do not use object initializer! _orientationHelper.Orientation = Orientation.Vertical; - //_orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e); - //_orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e); // This sets the width/height etc... OnOrientationChanged (Orientation); @@ -80,26 +78,13 @@ public class ScrollBar : View, IOrientation, IDesignable /// protected override void OnFrameChanged (in Rectangle frame) { - if (Orientation == Orientation.Vertical) - { - _slider.VisibleContentSize = Viewport.Height; - } - else - { - _slider.VisibleContentSize = Viewport.Width; - } - _slider.Size = CalculateSliderSize (); + ShowHide (); } private void ShowHide () { - if (!AutoHide) - { - return; - } - if (Orientation == Orientation.Vertical) { Visible = Frame.Height < ScrollableContentSize; @@ -109,6 +94,20 @@ public class ScrollBar : View, IOrientation, IDesignable Visible = Frame.Width < ScrollableContentSize; } + if (!AutoHide) + { + Visible = true; + } + + if (Orientation == Orientation.Vertical) + { + _slider.VisibleContentSize = Viewport.Height; + } + else + { + _slider.VisibleContentSize = Viewport.Width; + } + _slider.Size = CalculateSliderSize (); _sliderPosition = CalculateSliderPositionFromContentPosition (_position, NavigationDirection.Forward); _slider.Position = _sliderPosition.Value; @@ -188,13 +187,17 @@ public class ScrollBar : View, IOrientation, IDesignable if (Orientation == Orientation.Vertical) { + Height = Dim.Func (() => SuperView?.Viewport.Height ?? 0); Width = 1; - Height = Dim.Fill (); + X = Pos.AnchorEnd (); + Y = 0; } else { - Width = Dim.Fill (); + Width = Dim.Func (() => SuperView?.Viewport.Width ?? 0); Height = 1; + X = 0; + Y = Pos.AnchorEnd (); } _slider.Orientation = newOrientation; @@ -279,17 +282,26 @@ public class ScrollBar : View, IOrientation, IDesignable { _visibleContentSize = value; _slider.Size = CalculateSliderSize (); + ShowHide (); } } - private int _scrollableContentSize; + private int? _scrollableContentSize; /// /// Gets or sets the size of the content that can be scrolled. This is typically set to . /// public int ScrollableContentSize { - get => _scrollableContentSize; + get + { + if (_scrollableContentSize.HasValue) + { + return _scrollableContentSize.Value; + } + return Orientation == Orientation.Vertical ? SuperView?.GetContentSize().Height ?? 0 : SuperView?.GetContentSize ().Width ?? 0; + + } set { if (value == _scrollableContentSize || value < 0) @@ -299,8 +311,9 @@ public class ScrollBar : View, IOrientation, IDesignable _scrollableContentSize = value; _slider.Size = CalculateSliderSize (); - OnSizeChanged (_scrollableContentSize); - ScrollableContentSizeChanged?.Invoke (this, new (in _scrollableContentSize)); + ShowHide(); + OnSizeChanged (value); + ScrollableContentSizeChanged?.Invoke (this, new (in value)); SetNeedsLayout (); } } @@ -370,6 +383,7 @@ public class ScrollBar : View, IOrientation, IDesignable OnScrolled (distance); Scrolled?.Invoke (this, new (in distance)); + SetNeedsLayout (); } } diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs index 8b16d0ea9..bf3418d8a 100644 --- a/UnitTests/Views/ScrollBarTests.cs +++ b/UnitTests/Views/ScrollBarTests.cs @@ -32,7 +32,6 @@ public class ScrollBarTests (ITestOutputHelper output) var scrollBar = new ScrollBar { - ScrollableContentSize = 20, }; super.Add (scrollBar); Assert.True (scrollBar.AutoHide); @@ -213,274 +212,6 @@ public class ScrollBarTests (ITestOutputHelper output) #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.CalculateSliderPositionFromContentPosition (contentPosition, NavigationDirection.Forward); - - // Assert - Assert.Equal (expectedSliderPosition, sliderPosition); - } - - - #endregion Slider #region Size @@ -489,97 +220,6 @@ public class ScrollBarTests (ITestOutputHelper output) #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.CalculatePositionFromSliderPosition (sliderPosition); - - // Assert - Assert.Equal (expectedContentPosition, contentPosition); - } [Fact] public void Position_Event_Cancelables () { @@ -694,7 +334,7 @@ public class ScrollBarTests (ITestOutputHelper output) Orientation.Horizontal, @" ┌──────────┐ -│◄████░░░░►│ +│◄░████░░░►│ └──────────┘ ")] @@ -741,7 +381,7 @@ public class ScrollBarTests (ITestOutputHelper output) Orientation.Horizontal, @" ┌──────────┐ -│◄░░░████░►│ +│◄░░████░░►│ └──────────┘ ")] @@ -766,7 +406,7 @@ public class ScrollBarTests (ITestOutputHelper output) Orientation.Horizontal, @" ┌──────────┐ -│◄░░░░████►│ +│◄░░░████░►│ └──────────┘ ")] @@ -885,7 +525,7 @@ public class ScrollBarTests (ITestOutputHelper output) Orientation.Horizontal, @" ┌────────────┐ -│◄░██████░░░►│ +│◄░░██████░░►│ └────────────┘ ")] @@ -908,7 +548,7 @@ public class ScrollBarTests (ITestOutputHelper output) Orientation.Horizontal, @" ┌────────────┐ -│◄░░░██████░►│ +│◄░░██████░░►│ └────────────┘ ")] @@ -1087,13 +727,13 @@ public class ScrollBarTests (ITestOutputHelper output) │▲│ │░│ │░│ +│█│ +│█│ +│█│ +│█│ +│█│ +│█│ │░│ -│█│ -│█│ -│█│ -│█│ -│█│ -│█│ │░│ │▼│ └─┘")] @@ -1121,39 +761,35 @@ public class ScrollBarTests (ITestOutputHelper output) #endregion Vertical - public void Draws_Correctly (int width, int height, int contentSize, int contentPosition, Orientation orientation, string expected) + public void Draws_Correctly_Default_Settings (int width, int height, int contentSize, int contentPosition, Orientation orientation, string expected) { var super = new Window { Id = "super", Width = width + 2, - Height = height + 2 + Height = height + 2, }; var scrollBar = new ScrollBar { + AutoHide = false, Orientation = orientation, }; if (orientation == Orientation.Vertical) { - scrollBar.Width = 1; - scrollBar.Height = height; + super.SetContentSize (new (width, contentSize)); + scrollBar.Width = width; } else { - scrollBar.Width = width; - scrollBar.Height = 1; + super.SetContentSize (new (contentSize, height)); + scrollBar.Height = height; } super.Add (scrollBar); - scrollBar.ScrollableContentSize = contentSize; scrollBar.Position = contentPosition; - int sliderPos = scrollBar.CalculateSliderPositionFromContentPosition (contentPosition, NavigationDirection.Forward); - - super.BeginInit (); - super.EndInit (); super.Layout (); super.Draw (); @@ -1188,7 +824,7 @@ public class ScrollBarTests (ITestOutputHelper output) RunState rs = Application.Begin (top); // Scroll to end - scrollBar.Position = 20; + scrollBar.Position = 19; Assert.Equal (10, scrollBar.Position); Application.RunIteration (ref rs); @@ -1196,6 +832,15 @@ public class ScrollBarTests (ITestOutputHelper output) Assert.Equal (10, scrollBar.Position); int initialPos = scrollBar.Position; + Point btnPoint = orientation == Orientation.Vertical + ? new (scrollBar.Frame.X, 0) + : new (0, scrollBar.Frame.Y); + + Application.RaiseMouseEvent (new () + { + ScreenPosition = btnPoint, + Flags = MouseFlags.Button1Clicked + }); Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), @@ -1239,9 +884,13 @@ public class ScrollBarTests (ITestOutputHelper output) Assert.Equal (0, scrollBar.Position); int initialPos = scrollBar.Position; + Point btnPoint = orientation == Orientation.Vertical + ? new (scrollBar.Frame.X, scrollBar.Frame.Height - 1) + : new (scrollBar.Frame.Width - 1, scrollBar.Frame.Y); + Application.RaiseMouseEvent (new () { - ScreenPosition = orientation == Orientation.Vertical ? new (0, scrollBar.Frame.Height - 1) : new (scrollBar.Frame.Width - 1, 0), + ScreenPosition = btnPoint, Flags = MouseFlags.Button1Clicked }); Application.RunIteration (ref rs); diff --git a/UnitTests/Views/ScrollSliderTests.cs b/UnitTests/Views/ScrollSliderTests.cs index 194645add..997c66ffc 100644 --- a/UnitTests/Views/ScrollSliderTests.cs +++ b/UnitTests/Views/ScrollSliderTests.cs @@ -303,8 +303,8 @@ public class ScrollSliderTests (ITestOutputHelper output) // █░ [InlineData (2, 5, 1, 0)] // --^-- - // █░ - [InlineData (2, 5, 2, 0)] + // ░█ + [InlineData (2, 5, 2, 1)] // ---^- // ░█ [InlineData (2, 5, 3, 1)] @@ -336,7 +336,7 @@ public class ScrollSliderTests (ITestOutputHelper output) [InlineData (3, 6, 0, 0)] // -^---- // █░░ - [InlineData (3, 6, 1, 0)] + [InlineData (3, 6, 1, 1)] // --^--- // ░█░ [InlineData (3, 6, 2, 1)] @@ -442,11 +442,11 @@ public class ScrollSliderTests (ITestOutputHelper output) // ░███░░░░ // --^--------------- [InlineData (8, 18, 2, 1)] - [InlineData (8, 18, 3, 1)] + [InlineData (8, 18, 3, 2)] [InlineData (8, 18, 4, 2)] [InlineData (8, 18, 5, 2)] [InlineData (8, 18, 6, 3)] - [InlineData (8, 18, 7, 3)] + [InlineData (8, 18, 7, 4)] [InlineData (8, 18, 8, 4)] [InlineData (8, 18, 9, 4)] @@ -457,7 +457,7 @@ public class ScrollSliderTests (ITestOutputHelper output) [InlineData (8, 18, 11, 5)] [InlineData (8, 18, 12, 5)] [InlineData (8, 18, 13, 5)] - [InlineData (8, 18, 14, 4)] + [InlineData (8, 18, 14, 5)] [InlineData (8, 18, 15, 5)] [InlineData (8, 18, 16, 5)] [InlineData (8, 18, 17, 5)]