From 478c93d985176e256d7452a0846a0579d04b794f Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 23 May 2024 22:42:08 +0100 Subject: [PATCH 001/128] Create a Scroll class with unit tests and use case. --- Terminal.Gui/Views/Scroll.cs | 373 ++++++++++++++ UICatalog/Scenarios/ScrollDemo.cs | 195 ++++++++ UnitTests/Views/ScrollTests.cs | 802 ++++++++++++++++++++++++++++++ 3 files changed, 1370 insertions(+) create mode 100644 Terminal.Gui/Views/Scroll.cs create mode 100644 UICatalog/Scenarios/ScrollDemo.cs create mode 100644 UnitTests/Views/ScrollTests.cs diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs new file mode 100644 index 0000000000..7422732003 --- /dev/null +++ b/Terminal.Gui/Views/Scroll.cs @@ -0,0 +1,373 @@ +// +// Scroll.cs: Vertical or horizontal scroll +// +// Author: BDisp +// +// Licensed under the MIT license +// + +namespace Terminal.Gui; + +/// +/// Represents the "inside part" of a scroll bar, minus the arrows. +/// +public class Scroll : View +{ + /// + public Scroll () + { + WantContinuousButtonPressed = true; + ClearOnVisibleFalse = false; + CanFocus = false; + Orientation = Orientation.Vertical; + + _sliderContainer = new () { Width = Dim.Fill (), Height = Dim.Fill (), WantContinuousButtonPressed = true }; + _slider = new () { Id = "slider" }; + _sliderContainer.Add (_slider); + Add (_sliderContainer); + + Added += Scroll_Added; + Removed += Scroll_Removed; + Initialized += Scroll_Initialized; + _sliderContainer.DrawContent += SubViews_DrawContent; + _sliderContainer.MouseEvent += SliderContainer_MouseEvent; + _slider.DrawContent += SubViews_DrawContent; + _slider.MouseEvent += Slider_MouseEvent; + } + + private readonly View _sliderContainer; + private readonly View _slider; + private int _lastLocation = -1; + + private Orientation _orientation; + private int _size; + private int _position; + + private bool _wasSliderMouse; + + /// + /// Determines the Orientation of the scroll. + /// + public Orientation Orientation + { + get => _orientation; + set + { + _orientation = value; + SetWidthHeight (); + } + } + + /// + /// The position, relative to , to set the scrollbar at. + /// + public int Position + { + get => _position; + set + { + int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; + + if (value < 0 || (value > 0 && value + barSize > Size)) + { + return; + } + + StateEventArgs args = OnPositionChanging (_position, value); + + if (args.Cancel) + { + return; + } + + int oldPos = _position; + _position = value; + OnPositionChanged (oldPos); + + if (!_wasSliderMouse) + { + SetWidthHeight (); + } + } + } + + /// This event is raised when the position on the scrollbar has changed. + public event EventHandler> PositionChanged; + + /// This event is raised when the position on the scrollbar is changing. + public event EventHandler> PositionChanging; + + /// + /// The size of content the scroll represents. + /// + public int Size + { + get => _size; + set + { + int oldSize = _size; + _size = value; + OnSizeChanged (oldSize); + SetWidthHeight (); + } + } + + /// This event is raised when the size of the scroll has changed. + public event EventHandler> SizeChanged; + + /// + protected override void Dispose (bool disposing) + { + Added -= Scroll_Added; + Initialized -= Scroll_Initialized; + _sliderContainer.DrawContent -= SubViews_DrawContent; + _sliderContainer.MouseEvent -= SliderContainer_MouseEvent; + _slider.DrawContent -= SubViews_DrawContent; + _slider.MouseEvent -= Slider_MouseEvent; + + base.Dispose (disposing); + } + + /// Virtual method to invoke the event handler. + protected virtual void OnPositionChanged (int oldPos) { PositionChanged?.Invoke (this, new (oldPos, Position)); } + + /// Virtual method to invoke the cancelable event handler. + protected virtual StateEventArgs OnPositionChanging (int oldPos, int newPos) + { + StateEventArgs args = new (oldPos, newPos); + PositionChanging?.Invoke (this, args); + + return args; + } + + /// Virtual method to invoke the event handler. + protected void OnSizeChanged (int oldSize) { SizeChanged?.Invoke (this, new (oldSize, Size)); } + + private int GetPositionFromSliderLocation (int location) + { + if (Frame.Height == 0 || Frame.Width == 0) + { + return 0; + } + + int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; + + return Math.Min (location * Size / barSize, Size - barSize); + } + + private (int Location, int Dimension) GetSliderLocationDimensionFromPosition () + { + if (Frame.Height == 0 || Frame.Width == 0) + { + return new (0, 0); + } + + int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; + int location; + int dimension; + + if (Size > 0) + { + dimension = Math.Min (Math.Max (barSize * barSize / Size, 1), barSize); + + // Ensure the Position is valid + if (Position > 0 && Position + barSize > Size) + { + Position = Size - barSize; + } + + location = Math.Min (Position * barSize / Size, barSize - dimension); + + if (Position == Size - barSize && location + dimension < barSize) + { + location = barSize - dimension; + } + } + else + { + location = 0; + dimension = barSize; + } + + return new (location, dimension); + } + + private void Parent_LayoutComplete (object sender, LayoutEventArgs e) + { + if (!_wasSliderMouse) + { + SetWidthHeight (); + } + else + { + _wasSliderMouse = false; + } + } + + private void Parent_MouseEnter (object sender, MouseEventEventArgs e) { OnMouseEnter (e.MouseEvent); } + + private void Parent_MouseLeave (object sender, MouseEventEventArgs e) { OnMouseLeave (e.MouseEvent); } + + private void Scroll_Added (object sender, SuperViewChangedEventArgs e) + { + View parent = e.Parent is Adornment adornment ? adornment.Parent : e.Parent; + + parent.LayoutComplete += Parent_LayoutComplete; + parent.MouseEnter += Parent_MouseEnter; + parent.MouseLeave += Parent_MouseLeave; + } + + private void Scroll_Initialized (object sender, EventArgs e) { SetWidthHeight (); } + + private void Scroll_Removed (object sender, SuperViewChangedEventArgs e) + { + if (e.Parent is { }) + { + View parent = e.Parent is Adornment adornment ? adornment.Parent : e.Parent; + + parent.LayoutComplete -= Parent_LayoutComplete; + parent.MouseEnter -= Parent_MouseEnter; + parent.MouseLeave -= Parent_MouseLeave; + } + } + + private static void SetColorSchemeWithSuperview (View view) + { + if (view.SuperView is { }) + { + View parent = view.SuperView is Adornment adornment ? adornment.Parent : view.SuperView; + + if (view.Id == "slider") + { + view.ColorScheme = new () { Normal = new (parent.ColorScheme.Normal.Foreground, parent.ColorScheme.Normal.Foreground) }; + } + else + { + view.ColorScheme = parent.ColorScheme; + } + } + } + + private void SetSliderText () + { + _sliderContainer.TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; + + _sliderContainer.Text = string.Concat ( + Enumerable.Repeat ( + Glyphs.Stipple.ToString (), + Orientation == Orientation.Vertical + ? _sliderContainer.Frame.Height + : _sliderContainer.Frame.Width)); + _slider.TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; + + _slider.Text = string.Concat ( + Enumerable.Repeat ( + Glyphs.ContinuousMeterSegment.ToString (), + Orientation == Orientation.Vertical + ? _sliderContainer.Frame.Height + : _slider.Frame.Width)); + } + + private void SetWidthHeight () + { + if (Orientation == Orientation.Vertical) + { + Width = 1; + } + else + { + Height = 1; + } + + if (!IsInitialized) + { + return; + } + + _sliderContainer.Width = Orientation == Orientation.Vertical ? 1 : Dim.Fill (); + _sliderContainer.Height = Orientation == Orientation.Vertical ? Dim.Fill () : 1; + + (int Location, int Dimension) slider = GetSliderLocationDimensionFromPosition (); + _slider.X = Orientation == Orientation.Vertical ? 0 : slider.Location; + _slider.Y = Orientation == Orientation.Vertical ? slider.Location : 0; + _slider.Width = Orientation == Orientation.Vertical ? 1 : slider.Dimension; + _slider.Height = Orientation == Orientation.Vertical ? slider.Dimension : 1; + + SetSliderText (); + } + + private void Slider_MouseEvent (object sender, MouseEventEventArgs e) + { + MouseEvent me = e.MouseEvent; + int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; + int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; + int offset = _lastLocation > -1 ? location - _lastLocation : 0; + + if (me.Flags == MouseFlags.Button1Pressed) + { + if (Application.MouseGrabView != sender as View) + { + Application.GrabMouse (sender as View); + _lastLocation = location; + } + } + else if (me.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) + { + if (Orientation == Orientation.Vertical) + { + if (_slider.Frame.Y + offset >= 0 && _slider.Frame.Y + offset + _slider.Frame.Height <= barSize) + { + _wasSliderMouse = true; + _slider.Y = _slider.Frame.Y + offset; + Position = GetPositionFromSliderLocation (_slider.Frame.Y); + } + } + else + { + if (_slider.Frame.X + offset >= 0 && _slider.Frame.X + offset + _slider.Frame.Width <= barSize) + { + _wasSliderMouse = true; + _slider.X = _slider.Frame.X + offset; + Position = GetPositionFromSliderLocation (_slider.Frame.X); + } + } + } + else if (me.Flags == MouseFlags.Button1Released) + { + _lastLocation = -1; + + if (Application.MouseGrabView == sender as View) + { + Application.UngrabMouse (); + } + } + else + { + return; + } + + e.Handled = true; + } + + private void SliderContainer_MouseEvent (object sender, MouseEventEventArgs e) + { + MouseEvent me = e.MouseEvent; + int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; + int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; + + (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical + ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) + : new (_slider.Frame.X, _slider.Frame.Right - 1); + + if (me.Flags == MouseFlags.Button1Pressed && location < sliderPos.topLeft) + { + Position = Math.Max (Position - barSize, 0); + } + else if (me.Flags == MouseFlags.Button1Pressed && location > sliderPos.bottomRight) + { + Position = Math.Min (Position + barSize, Size - barSize); + } + } + + private void SubViews_DrawContent (object sender, DrawEventArgs e) { SetColorSchemeWithSuperview (sender as View); } +} diff --git a/UICatalog/Scenarios/ScrollDemo.cs b/UICatalog/Scenarios/ScrollDemo.cs new file mode 100644 index 0000000000..0566faa88d --- /dev/null +++ b/UICatalog/Scenarios/ScrollDemo.cs @@ -0,0 +1,195 @@ +using Terminal.Gui; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("Scroll Demo", "Demonstrates using Scroll view.")] +[ScenarioCategory ("Drawing")] +[ScenarioCategory ("Scrolling")] +public class ScrollDemo : Scenario +{ + private ViewDiagnosticFlags _diagnosticFlags; + + public override void Main () + { + Application.Init (); + + _diagnosticFlags = View.Diagnostics; + + Window app = new () + { + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" + }; + + var editor = new Adornments.AdornmentsEditor (); + app.Add (editor); + + var view = new FrameView + { + Title = "Demo View", + X = Pos.Right (editor), + Width = Dim.Fill (), + Height = Dim.Fill (), + ColorScheme = Colors.ColorSchemes ["Base"] + }; + app.Add (view); + + var scroll = new Scroll + { + X = Pos.AnchorEnd (), + Width = Dim.Fill (), + Height = Dim.Fill () + }; + view.Add (scroll); + + var rgOrientation = new RadioGroup + { + RadioLabels = ["Vertical", "Horizontal"], + Orientation = Orientation.Horizontal + }; + view.Add (rgOrientation); + + rgOrientation.SelectedItemChanged += (s, e) => + { + if (e.SelectedItem == e.PreviousSelectedItem) + { + return; + } + + if (rgOrientation.SelectedItem == 0) + { + scroll.Orientation = Orientation.Vertical; + scroll.X = Pos.AnchorEnd (); + scroll.Height = Dim.Fill (); + scroll.Size /= 3; + } + else + { + scroll.Orientation = Orientation.Horizontal; + scroll.Y = Pos.AnchorEnd (); + scroll.Width = Dim.Fill (); + scroll.Size *= 3; + } + }; + + var lblSize = new Label + { + Y = Pos.Bottom (rgOrientation), + Text = "Size:" + }; + view.Add (lblSize); + + Buttons.NumericUpDown scrollSize = new Buttons.NumericUpDown + { + Value = scroll.Size, + X = Pos.Right (lblSize) + 1, + Y = Pos.Top (lblSize) + }; + view.Add (scrollSize); + + scrollSize.ValueChanging += (s, e) => + { + if (e.NewValue < 0) + { + e.Cancel = true; + + return; + } + + if (scroll.Size != e.NewValue) + { + scroll.Size = e.NewValue; + } + }; + + var lblPosition = new Label + { + Y = Pos.Bottom (lblSize), + Text = "Position:" + }; + view.Add (lblPosition); + + Buttons.NumericUpDown scrollPosition = new Buttons.NumericUpDown + { + Value = scroll.Position, + X = Pos.Right (lblPosition) + 1, + Y = Pos.Top (lblPosition) + }; + view.Add (scrollPosition); + + scrollPosition.ValueChanging += (s, e) => + { + if (e.NewValue < 0) + { + e.Cancel = true; + + return; + } + + if (scroll.Position != e.NewValue) + { + scroll.Position = e.NewValue; + } + + if (scroll.Position != e.NewValue) + { + e.Cancel = true; + } + }; + + var lblSizeChanged = new Label + { + Y = Pos.Bottom (lblPosition) + 1 + }; + view.Add (lblSizeChanged); + + scroll.SizeChanged += (s, e) => + { + lblSizeChanged.Text = $"SizeChanged event - OldValue: {e.OldValue}; NewValue: {e.NewValue}"; + + if (scrollSize.Value != e.NewValue) + { + scrollSize.Value = e.NewValue; + } + }; + + var lblPosChanging = new Label + { + Y = Pos.Bottom (lblSizeChanged) + }; + view.Add (lblPosChanging); + + scroll.PositionChanging += (s, e) => { lblPosChanging.Text = $"PositionChanging event - OldValue: {e.OldValue}; NewValue: {e.NewValue}"; }; + + var lblPositionChanged = new Label + { + Y = Pos.Bottom (lblPosChanging) + }; + view.Add (lblPositionChanged); + + scroll.PositionChanged += (s, e) => + { + lblPositionChanged.Text = $"PositionChanged event - OldValue: {e.OldValue}; NewValue: {e.NewValue}"; + scrollPosition.Value = e.NewValue; + }; + + var lblScrollFrame = new Label + { + Y = Pos.Bottom (lblPositionChanged) + 1 + }; + view.Add (lblScrollFrame); + + scroll.LayoutComplete += (s, e) => lblScrollFrame.Text = $"Scroll Frame: {scroll.Frame.ToString ()}"; + + editor.Initialized += (s, e) => + { + scroll.Size = 40; + editor.ViewToEdit = view; + }; + + app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags; + + Application.Run (app); + app.Dispose (); + Application.Shutdown (); + } +} diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs new file mode 100644 index 0000000000..b5f9bf8079 --- /dev/null +++ b/UnitTests/Views/ScrollTests.cs @@ -0,0 +1,802 @@ +using Xunit.Abstractions; + +namespace Terminal.Gui.ViewsTests; + +public class ScrollTests +{ + public ScrollTests (ITestOutputHelper output) { _output = output; } + private readonly ITestOutputHelper _output; + + [Theory] + [AutoInitShutdown] + [InlineData ( + 20, + @" +█ +█ +█ +█ +█ +░ +░ +░ +░ +░", + @" +░ +░ +█ +█ +█ +█ +█ +░ +░ +░", + @" +░ +░ +░ +░ +░ +█ +█ +█ +█ +█", + @" +░ +░ +█ +█ +░ +░ +░ +░ +░ +░", + @" +█████░░░░░", + @" +░░█████░░░", + @" +░░░░░█████", + @" +░░██░░░░░░")] + [InlineData ( + 40, + @" +█ +█ +░ +░ +░ +░ +░ +░ +░ +░", + @" +░ +█ +█ +░ +░ +░ +░ +░ +░ +░", + @" +░ +░ +█ +█ +░ +░ +░ +░ +░ +░", + @" +░ +█ +░ +░ +░ +░ +░ +░ +░ +░", + @" +██░░░░░░░░", + @" +░██░░░░░░░", + @" +░░██░░░░░░", + @" +░█░░░░░░░░")] + public void Changing_Position_Size_Orientation_Draws_Correctly ( + int size, + string firstVertExpected, + string middleVertExpected, + string endVertExpected, + string sizeVertExpected, + string firstHoriExpected, + string middleHoriExpected, + string endHoriExpected, + string sizeHoriExpected + ) + { + var scroll = new Scroll + { + Orientation = Orientation.Vertical, + Size = size, + Height = 10 + }; + var top = new Toplevel (); + top.Add (scroll); + Application.Begin (top); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (firstVertExpected, _output); + + scroll.Position = 4; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (middleVertExpected, _output); + + scroll.Position = 10; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (endVertExpected, _output); + + scroll.Size = size * 2; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (sizeVertExpected, _output); + + scroll.Orientation = Orientation.Horizontal; + scroll.Width = 10; + scroll.Position = 0; + scroll.Size = size; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (firstHoriExpected, _output); + + scroll.Position = 4; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (middleHoriExpected, _output); + + scroll.Position = 10; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (endHoriExpected, _output); + + scroll.Size = size * 2; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (sizeHoriExpected, _output); + } + + [Fact] + public void Constructor_Defaults () + { + var scroll = new Scroll (); + Assert.True (scroll.WantContinuousButtonPressed); + Assert.False (scroll.ClearOnVisibleFalse); + Assert.False (scroll.CanFocus); + Assert.Equal (Orientation.Vertical, scroll.Orientation); + Assert.Equal (0, scroll.Size); + Assert.Equal (0, scroll.Position); + } + + [Theory] + [AutoInitShutdown] + [InlineData ( + Orientation.Vertical, + 20, + 10, + 4, + @" +░ +░ +░ +░ +░ +█ +█ +█ +█ +█", + 0, + @" +█ +█ +█ +█ +█ +░ +░ +░ +░ +░")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 5, + @" +░ +░ +█ +█ +░ +░ +░ +░ +░ +░", + 20, + @" +░ +░ +░ +░ +░ +█ +█ +░ +░ +░")] + [InlineData ( + Orientation.Horizontal, + 20, + 10, + 4, + @" +░░░░░█████", + 0, + @" +█████░░░░░")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 5, + @" +░░██░░░░░░", + 20, + @" +░░░░░██░░░")] + public void Mouse_On_The_Container (Orientation orientation, int size, int position, int location, string output, int expectedPos, string expectedOut) + { + var scroll = new Scroll { Width = 10, Height = 10, Orientation = orientation, Size = size, Position = position }; + var top = new Toplevel (); + top.Add (scroll); + Application.Begin (top); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (output, _output); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, location) : new Point (location, 0), + Flags = MouseFlags.Button1Pressed + }); + Assert.Equal (expectedPos, scroll.Position); + + Application.Refresh (); + _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); + } + + [Theory] + [AutoInitShutdown] + [InlineData ( + Orientation.Vertical, + 20, + 10, + 5, + 5, + @" +░ +░ +░ +░ +░ +█ +█ +█ +█ +█", + MouseFlags.Button1Pressed, + 10, + @" +░ +░ +░ +░ +░ +█ +█ +█ +█ +█")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 3, + 3, + @" +░ +░ +█ +█ +░ +░ +░ +░ +░ +░", + MouseFlags.Button1Pressed, + 10, + @" +░ +░ +█ +█ +░ +░ +░ +░ +░ +░")] + [InlineData ( + Orientation.Horizontal, + 20, + 10, + 5, + 5, + @" +░░░░░█████", + MouseFlags.Button1Pressed, + 10, + @" +░░░░░█████")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 3, + 3, + @" +░░██░░░░░░", + MouseFlags.Button1Pressed, + 10, + @" +░░██░░░░░░")] + [InlineData ( + Orientation.Vertical, + 20, + 10, + 5, + 4, + @" +░ +░ +░ +░ +░ +█ +█ +█ +█ +█", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 8, + @" +░ +░ +░ +░ +█ +█ +█ +█ +█ +░")] + [InlineData ( + Orientation.Horizontal, + 20, + 10, + 5, + 4, + @" +░░░░░█████", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 8, + @" +░░░░█████░")] + [InlineData ( + Orientation.Vertical, + 20, + 10, + 5, + 6, + @" +░ +░ +░ +░ +░ +█ +█ +█ +█ +█", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 10, + @" +░ +░ +░ +░ +░ +█ +█ +█ +█ +█")] + [InlineData ( + Orientation.Horizontal, + 20, + 10, + 5, + 6, + @" +░░░░░█████", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 10, + @" +░░░░░█████")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 2, + 1, + @" +░ +░ +█ +█ +░ +░ +░ +░ +░ +░", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 4, + @" +░ +█ +█ +░ +░ +░ +░ +░ +░ +░")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 2, + 1, + @" +░░██░░░░░░", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 4, + @" +░██░░░░░░░")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 3, + 4, + @" +░ +░ +█ +█ +░ +░ +░ +░ +░ +░", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 12, + @" +░ +░ +░ +█ +█ +░ +░ +░ +░ +░")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 3, + 4, + @" +░░██░░░░░░", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 12, + @" +░░░██░░░░░")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 2, + 3, + @" +░ +░ +█ +█ +░ +░ +░ +░ +░ +░", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 12, + @" +░ +░ +░ +█ +█ +░ +░ +░ +░ +░")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 2, + 3, + @" +░░██░░░░░░", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 12, + @" +░░░██░░░░░")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 2, + 4, + @" +░ +░ +█ +█ +░ +░ +░ +░ +░ +░", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 16, + @" +░ +░ +░ +░ +█ +█ +░ +░ +░ +░")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 2, + 4, + @" +░░██░░░░░░", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 16, + @" +░░░░██░░░░")] + public void Mouse_On_The_Slider ( + Orientation orientation, + int size, + int position, + int startLocation, + int endLocation, + string output, + MouseFlags mouseFlags, + int expectedPos, + string expectedOut + ) + { + var scroll = new Scroll { Width = 10, Height = 10, Orientation = orientation, Size = size, Position = position }; + var top = new Toplevel (); + top.Add (scroll); + Application.Begin (top); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (output, _output); + + Assert.Null (Application.MouseGrabView); + + if (mouseFlags.HasFlag (MouseFlags.ReportMousePosition)) + { + MouseFlags mf = mouseFlags & ~MouseFlags.ReportMousePosition; + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), + Flags = mf + }); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, endLocation) : new (endLocation, 0), + Flags = mouseFlags + }); + } + else + { + Assert.Equal (startLocation, endLocation); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), + Flags = mouseFlags + }); + } + + Assert.Equal ("slider", Application.MouseGrabView?.Id); + Assert.Equal (expectedPos, scroll.Position); + + Application.Refresh (); + _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), + Flags = MouseFlags.Button1Released + }); + Assert.Null (Application.MouseGrabView); + } + + [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) + { + var scroll = new Scroll { Orientation = orientation, Height = 10, Size = size }; + Assert.Equal (0, scroll.Position); + + scroll.Position = -1; + Assert.Equal (0, scroll.Position); + + scroll.Position = size; + Assert.Equal (0, scroll.Position); + + scroll.Position = expectedPos; + Assert.Equal (expectedPos, scroll.Position); + } + + [Fact] + public void PositionChanging_Cancelable_And_PositionChanged_Events () + { + var changingCount = 0; + var changedCount = 0; + var scroll = new Scroll { Size = 10 }; + + scroll.PositionChanging += (s, e) => + { + if (changingCount == 0) + { + e.Cancel = true; + } + + changingCount++; + }; + scroll.PositionChanged += (s, e) => changedCount++; + + scroll.Position = 1; + Assert.Equal (0, scroll.Position); + Assert.Equal (1, changingCount); + Assert.Equal (0, changedCount); + + scroll.Position = 1; + Assert.Equal (1, scroll.Position); + Assert.Equal (2, changingCount); + Assert.Equal (1, changedCount); + } + + [Fact] + public void SizeChanged_Event () + { + var count = 0; + var scroll = new Scroll (); + scroll.SizeChanged += (s, e) => count++; + + scroll.Size = 10; + Assert.Equal (10, scroll.Size); + Assert.Equal (1, count); + } + + [Theory] + [AutoInitShutdown] + [InlineData ( + 3, + 10, + Orientation.Vertical, + @" +┌─┐ +│█│ +│█│ +│█│ +│░│ +│░│ +│░│ +│░│ +│░│ +└─┘")] + [InlineData ( + 10, + 3, + Orientation.Horizontal, + @" +┌────────┐ +│███░░░░░│ +└────────┘")] + public void Vertical_Horizontal_Draws_Correctly (int width, int height, Orientation orientation, string expected) + { + var super = new Window { Id = "super", Width = Dim.Fill (), Height = Dim.Fill () }; + var top = new Toplevel (); + top.Add (super); + + var scroll = new Scroll + { + Orientation = orientation, + Size = orientation == Orientation.Vertical ? height * 2 : width * 2, + Width = orientation == Orientation.Vertical ? 1 : Dim.Fill (), + Height = orientation == Orientation.Vertical ? Dim.Fill () : 1 + }; + super.Add (scroll); + + Application.Begin (top); + ((FakeDriver)Application.Driver).SetBufferSize (width, height); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + } +} From 84c69f0ffecac6d9b5b835a766f3cb52956fc21f Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 24 May 2024 16:01:35 +0100 Subject: [PATCH 002/128] Remove unnecessary _sliderContainer view. --- Terminal.Gui/Views/Scroll.cs | 48 ++++++++----------------- UICatalog/Scenarios/ScrollDemo.cs | 42 ++++++++++++++++++++-- UnitTests/Views/ScrollTests.cs | 59 +++++++++++++++++++++++++++---- 3 files changed, 106 insertions(+), 43 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index 7422732003..c4afab2310 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -20,22 +20,20 @@ public Scroll () ClearOnVisibleFalse = false; CanFocus = false; Orientation = Orientation.Vertical; + Width = 1; - _sliderContainer = new () { Width = Dim.Fill (), Height = Dim.Fill (), WantContinuousButtonPressed = true }; _slider = new () { Id = "slider" }; - _sliderContainer.Add (_slider); - Add (_sliderContainer); + Add (_slider); Added += Scroll_Added; Removed += Scroll_Removed; Initialized += Scroll_Initialized; - _sliderContainer.DrawContent += SubViews_DrawContent; - _sliderContainer.MouseEvent += SliderContainer_MouseEvent; + DrawContent += SubViews_DrawContent; + MouseEvent += SliderContainer_MouseEvent; _slider.DrawContent += SubViews_DrawContent; _slider.MouseEvent += Slider_MouseEvent; } - private readonly View _sliderContainer; private readonly View _slider; private int _lastLocation = -1; @@ -120,8 +118,8 @@ protected override void Dispose (bool disposing) { Added -= Scroll_Added; Initialized -= Scroll_Initialized; - _sliderContainer.DrawContent -= SubViews_DrawContent; - _sliderContainer.MouseEvent -= SliderContainer_MouseEvent; + DrawContent -= SubViews_DrawContent; + MouseEvent -= SliderContainer_MouseEvent; _slider.DrawContent -= SubViews_DrawContent; _slider.MouseEvent -= Slider_MouseEvent; @@ -250,48 +248,32 @@ private static void SetColorSchemeWithSuperview (View view) private void SetSliderText () { - _sliderContainer.TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; - - _sliderContainer.Text = string.Concat ( - Enumerable.Repeat ( - Glyphs.Stipple.ToString (), - Orientation == Orientation.Vertical - ? _sliderContainer.Frame.Height - : _sliderContainer.Frame.Width)); + TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; + + Text = string.Concat ( + Enumerable.Repeat ( + Glyphs.Stipple.ToString (), + Frame.Width * Frame.Height)); _slider.TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; _slider.Text = string.Concat ( Enumerable.Repeat ( Glyphs.ContinuousMeterSegment.ToString (), - Orientation == Orientation.Vertical - ? _sliderContainer.Frame.Height - : _slider.Frame.Width)); + _slider.Frame.Width * _slider.Frame.Height)); } private void SetWidthHeight () { - if (Orientation == Orientation.Vertical) - { - Width = 1; - } - else - { - Height = 1; - } - if (!IsInitialized) { return; } - _sliderContainer.Width = Orientation == Orientation.Vertical ? 1 : Dim.Fill (); - _sliderContainer.Height = Orientation == Orientation.Vertical ? Dim.Fill () : 1; - (int Location, int Dimension) slider = GetSliderLocationDimensionFromPosition (); _slider.X = Orientation == Orientation.Vertical ? 0 : slider.Location; _slider.Y = Orientation == Orientation.Vertical ? slider.Location : 0; - _slider.Width = Orientation == Orientation.Vertical ? 1 : slider.Dimension; - _slider.Height = Orientation == Orientation.Vertical ? slider.Dimension : 1; + _slider.Width = Orientation == Orientation.Vertical ? Dim.Fill () : slider.Dimension; + _slider.Height = Orientation == Orientation.Vertical ? slider.Dimension : Dim.Fill (); SetSliderText (); } diff --git a/UICatalog/Scenarios/ScrollDemo.cs b/UICatalog/Scenarios/ScrollDemo.cs index 0566faa88d..77fbd7b75c 100644 --- a/UICatalog/Scenarios/ScrollDemo.cs +++ b/UICatalog/Scenarios/ScrollDemo.cs @@ -36,13 +36,47 @@ public override void Main () var scroll = new Scroll { X = Pos.AnchorEnd (), - Width = Dim.Fill (), + Width = 1, Height = Dim.Fill () }; view.Add (scroll); + var lblWidthHeight = new Label + { + Text = "Width/Height:" + }; + view.Add (lblWidthHeight); + + Buttons.NumericUpDown scrollWidthHeight = new() + { + Value = scroll.Frame.Width, + X = Pos.Right (lblWidthHeight) + 1, + Y = Pos.Top (lblWidthHeight) + }; + view.Add (scrollWidthHeight); + + scrollWidthHeight.ValueChanging += (s, e) => + { + if (e.NewValue < 1) + { + e.Cancel = true; + + return; + } + + if (scroll.Orientation == Orientation.Vertical) + { + scroll.Width = e.NewValue; + } + else + { + scroll.Height = e.NewValue; + } + }; + var rgOrientation = new RadioGroup { + Y = Pos.Bottom (lblWidthHeight), RadioLabels = ["Vertical", "Horizontal"], Orientation = Orientation.Horizontal }; @@ -59,6 +93,7 @@ public override void Main () { scroll.Orientation = Orientation.Vertical; scroll.X = Pos.AnchorEnd (); + scroll.Width = scrollWidthHeight.Value; scroll.Height = Dim.Fill (); scroll.Size /= 3; } @@ -67,6 +102,7 @@ public override void Main () scroll.Orientation = Orientation.Horizontal; scroll.Y = Pos.AnchorEnd (); scroll.Width = Dim.Fill (); + scroll.Height = scrollWidthHeight.Value; scroll.Size *= 3; } }; @@ -78,7 +114,7 @@ public override void Main () }; view.Add (lblSize); - Buttons.NumericUpDown scrollSize = new Buttons.NumericUpDown + Buttons.NumericUpDown scrollSize = new() { Value = scroll.Size, X = Pos.Right (lblSize) + 1, @@ -108,7 +144,7 @@ public override void Main () }; view.Add (lblPosition); - Buttons.NumericUpDown scrollPosition = new Buttons.NumericUpDown + Buttons.NumericUpDown scrollPosition = new() { Value = scroll.Position, X = Pos.Right (lblPosition) + 1, diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index b5f9bf8079..d4d77ffcca 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -158,6 +158,7 @@ string sizeHoriExpected scroll.Orientation = Orientation.Horizontal; scroll.Width = 10; + scroll.Height = 1; scroll.Position = 0; scroll.Size = size; Application.Refresh (); @@ -272,7 +273,13 @@ public void Constructor_Defaults () ░░░░░██░░░")] public void Mouse_On_The_Container (Orientation orientation, int size, int position, int location, string output, int expectedPos, string expectedOut) { - var scroll = new Scroll { Width = 10, Height = 10, Orientation = orientation, Size = size, Position = position }; + var scroll = new Scroll + { + Width = orientation == Orientation.Vertical ? 1 : 10, + Height = orientation == Orientation.Vertical ? 10 : 1, + Orientation = orientation, Size = size, + Position = position + }; var top = new Toplevel (); top.Add (scroll); Application.Begin (top); @@ -641,7 +648,13 @@ public void Mouse_On_The_Slider ( string expectedOut ) { - var scroll = new Scroll { Width = 10, Height = 10, Orientation = orientation, Size = size, Position = position }; + var scroll = new Scroll + { + Width = orientation == Orientation.Vertical ? 1 : 10, + Height = orientation == Orientation.Vertical ? 10 : 1, + Orientation = orientation, + Size = size, Position = position + }; var top = new Toplevel (); top.Add (scroll); Application.Begin (top); @@ -759,6 +772,7 @@ public void SizeChanged_Event () [InlineData ( 3, 10, + 1, Orientation.Vertical, @" ┌─┐ @@ -774,12 +788,40 @@ public void SizeChanged_Event () [InlineData ( 10, 3, + 1, Orientation.Horizontal, @" ┌────────┐ │███░░░░░│ └────────┘")] - public void Vertical_Horizontal_Draws_Correctly (int width, int height, Orientation orientation, string expected) + [InlineData ( + 3, + 10, + 3, + Orientation.Vertical, + @" +┌───┐ +│███│ +│███│ +│███│ +│░░░│ +│░░░│ +│░░░│ +│░░░│ +│░░░│ +└───┘")] + [InlineData ( + 10, + 3, + 3, + Orientation.Horizontal, + @" +┌────────┐ +│███░░░░░│ +│███░░░░░│ +│███░░░░░│ +└────────┘")] + public void Vertical_Horizontal_Draws_Correctly (int sizeWidth, int sizeHeight, int widthHeight, Orientation orientation, string expected) { var super = new Window { Id = "super", Width = Dim.Fill (), Height = Dim.Fill () }; var top = new Toplevel (); @@ -788,14 +830,17 @@ public void Vertical_Horizontal_Draws_Correctly (int width, int height, Orientat var scroll = new Scroll { Orientation = orientation, - Size = orientation == Orientation.Vertical ? height * 2 : width * 2, - Width = orientation == Orientation.Vertical ? 1 : Dim.Fill (), - Height = orientation == Orientation.Vertical ? Dim.Fill () : 1 + Size = orientation == Orientation.Vertical ? sizeHeight * 2 : sizeWidth * 2, + Width = orientation == Orientation.Vertical ? widthHeight : Dim.Fill (), + Height = orientation == Orientation.Vertical ? Dim.Fill () : widthHeight }; super.Add (scroll); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (width, height); + + ((FakeDriver)Application.Driver).SetBufferSize ( + sizeWidth + (orientation == Orientation.Vertical ? widthHeight - 1 : 0), + sizeHeight + (orientation == Orientation.Vertical ? 0 : widthHeight - 1)); _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); } From 4cb27fc2934f937d96f97a6c3ccf520ae69c6de5 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 24 May 2024 16:45:12 +0100 Subject: [PATCH 003/128] Rename to Scroll_ prefix on the Scroll event methods. --- Terminal.Gui/Views/Scroll.cs | 56 ++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index c4afab2310..09dd4925bf 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -28,9 +28,9 @@ public Scroll () Added += Scroll_Added; Removed += Scroll_Removed; Initialized += Scroll_Initialized; - DrawContent += SubViews_DrawContent; - MouseEvent += SliderContainer_MouseEvent; - _slider.DrawContent += SubViews_DrawContent; + DrawContent += Scroll_DrawContent; + MouseEvent += Scroll_MouseEvent; + _slider.DrawContent += Scroll_DrawContent; _slider.MouseEvent += Slider_MouseEvent; } @@ -118,9 +118,9 @@ protected override void Dispose (bool disposing) { Added -= Scroll_Added; Initialized -= Scroll_Initialized; - DrawContent -= SubViews_DrawContent; - MouseEvent -= SliderContainer_MouseEvent; - _slider.DrawContent -= SubViews_DrawContent; + DrawContent -= Scroll_DrawContent; + MouseEvent -= Scroll_MouseEvent; + _slider.DrawContent -= Scroll_DrawContent; _slider.MouseEvent -= Slider_MouseEvent; base.Dispose (disposing); @@ -215,8 +215,30 @@ private void Scroll_Added (object sender, SuperViewChangedEventArgs e) parent.MouseLeave += Parent_MouseLeave; } + private void Scroll_DrawContent (object sender, DrawEventArgs e) { SetColorSchemeWithSuperview (sender as View); } + private void Scroll_Initialized (object sender, EventArgs e) { SetWidthHeight (); } + private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) + { + MouseEvent me = e.MouseEvent; + int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; + int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; + + (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical + ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) + : new (_slider.Frame.X, _slider.Frame.Right - 1); + + if (me.Flags == MouseFlags.Button1Pressed && location < sliderPos.topLeft) + { + Position = Math.Max (Position - barSize, 0); + } + else if (me.Flags == MouseFlags.Button1Pressed && location > sliderPos.bottomRight) + { + Position = Math.Min (Position + barSize, Size - barSize); + } + } + private void Scroll_Removed (object sender, SuperViewChangedEventArgs e) { if (e.Parent is { }) @@ -330,26 +352,4 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) e.Handled = true; } - - private void SliderContainer_MouseEvent (object sender, MouseEventEventArgs e) - { - MouseEvent me = e.MouseEvent; - int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; - int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; - - (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical - ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) - : new (_slider.Frame.X, _slider.Frame.Right - 1); - - if (me.Flags == MouseFlags.Button1Pressed && location < sliderPos.topLeft) - { - Position = Math.Max (Position - barSize, 0); - } - else if (me.Flags == MouseFlags.Button1Pressed && location > sliderPos.bottomRight) - { - Position = Math.Min (Position + barSize, Size - barSize); - } - } - - private void SubViews_DrawContent (object sender, DrawEventArgs e) { SetColorSchemeWithSuperview (sender as View); } } From 46d17494905880d5957e9a54ad728586f89ad23c Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 24 May 2024 11:40:28 -0600 Subject: [PATCH 004/128] Tweaks and suggestions --- Terminal.Gui/Views/Scroll.cs | 52 ++++++++++++++++++++----------- UICatalog/Scenarios/ScrollDemo.cs | 11 +++---- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index 09dd4925bf..c9c81e55db 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -17,12 +17,18 @@ public class Scroll : View public Scroll () { WantContinuousButtonPressed = true; - ClearOnVisibleFalse = false; + //ClearOnVisibleFalse = false; CanFocus = false; Orientation = Orientation.Vertical; - Width = 1; + Width = Dim.Auto (DimAutoStyle.Content, 1); + Height = Dim.Auto (DimAutoStyle.Content); - _slider = new () { Id = "slider" }; + _slider = new () + { + Id = "slider", + Width = Dim.Auto (DimAutoStyle.Content), + Height = Dim.Auto (DimAutoStyle.Content) + }; Add (_slider); Added += Scroll_Added; @@ -35,14 +41,12 @@ public Scroll () } private readonly View _slider; - private int _lastLocation = -1; - private Orientation _orientation; - private int _size; - private int _position; + private int _lastLocation = -1; private bool _wasSliderMouse; + private Orientation _orientation; /// /// Determines the Orientation of the scroll. /// @@ -52,10 +56,11 @@ public Orientation Orientation set { _orientation = value; - SetWidthHeight (); + AdjustSlider(); } } + private int _position; /// /// The position, relative to , to set the scrollbar at. /// @@ -84,7 +89,7 @@ public int Position if (!_wasSliderMouse) { - SetWidthHeight (); + AdjustSlider (); } } } @@ -95,6 +100,7 @@ public int Position /// This event is raised when the position on the scrollbar is changing. public event EventHandler> PositionChanging; + private int _size; /// /// The size of content the scroll represents. /// @@ -106,7 +112,7 @@ public int Size int oldSize = _size; _size = value; OnSizeChanged (oldSize); - SetWidthHeight (); + AdjustSlider (); } } @@ -143,24 +149,24 @@ protected virtual StateEventArgs OnPositionChanging (int oldPos, int newPos private int GetPositionFromSliderLocation (int location) { - if (Frame.Height == 0 || Frame.Width == 0) + if (ContentSize.Height == 0 || ContentSize.Width == 0) { return 0; } - int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; + int barSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; return Math.Min (location * Size / barSize, Size - barSize); } private (int Location, int Dimension) GetSliderLocationDimensionFromPosition () { - if (Frame.Height == 0 || Frame.Width == 0) + if (ContentSize.Height == 0 || ContentSize.Width == 0) { return new (0, 0); } - int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; + int barSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; int location; int dimension; @@ -194,7 +200,7 @@ private void Parent_LayoutComplete (object sender, LayoutEventArgs e) { if (!_wasSliderMouse) { - SetWidthHeight (); + AdjustSlider (); } else { @@ -217,7 +223,10 @@ private void Scroll_Added (object sender, SuperViewChangedEventArgs e) private void Scroll_DrawContent (object sender, DrawEventArgs e) { SetColorSchemeWithSuperview (sender as View); } - private void Scroll_Initialized (object sender, EventArgs e) { SetWidthHeight (); } + private void Scroll_Initialized (object sender, EventArgs e) + { + AdjustSlider (); + } private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) { @@ -251,6 +260,7 @@ private void Scroll_Removed (object sender, SuperViewChangedEventArgs e) } } + // TODO: Just override GetNormalColor instead of having this method private static void SetColorSchemeWithSuperview (View view) { if (view.SuperView is { }) @@ -284,7 +294,7 @@ private void SetSliderText () _slider.Frame.Width * _slider.Frame.Height)); } - private void SetWidthHeight () + private void AdjustSlider () { if (!IsInitialized) { @@ -294,8 +304,12 @@ private void SetWidthHeight () (int Location, int Dimension) slider = GetSliderLocationDimensionFromPosition (); _slider.X = Orientation == Orientation.Vertical ? 0 : slider.Location; _slider.Y = Orientation == Orientation.Vertical ? slider.Location : 0; - _slider.Width = Orientation == Orientation.Vertical ? Dim.Fill () : slider.Dimension; - _slider.Height = Orientation == Orientation.Vertical ? slider.Dimension : Dim.Fill (); + + _slider.SetContentSize ( + new ( + Orientation == Orientation.Vertical ? ContentSize.Width : slider.Dimension, + Orientation == Orientation.Vertical ? slider.Dimension : ContentSize.Height + )); SetSliderText (); } diff --git a/UICatalog/Scenarios/ScrollDemo.cs b/UICatalog/Scenarios/ScrollDemo.cs index 77fbd7b75c..fcfa0012f0 100644 --- a/UICatalog/Scenarios/ScrollDemo.cs +++ b/UICatalog/Scenarios/ScrollDemo.cs @@ -36,8 +36,7 @@ public override void Main () var scroll = new Scroll { X = Pos.AnchorEnd (), - Width = 1, - Height = Dim.Fill () + Height = Dim.Fill (), }; view.Add (scroll); @@ -47,7 +46,7 @@ public override void Main () }; view.Add (lblWidthHeight); - Buttons.NumericUpDown scrollWidthHeight = new() + Buttons.NumericUpDown scrollWidthHeight = new () { Value = scroll.Frame.Width, X = Pos.Right (lblWidthHeight) + 1, @@ -114,7 +113,7 @@ public override void Main () }; view.Add (lblSize); - Buttons.NumericUpDown scrollSize = new() + Buttons.NumericUpDown scrollSize = new () { Value = scroll.Size, X = Pos.Right (lblSize) + 1, @@ -144,7 +143,7 @@ public override void Main () }; view.Add (lblPosition); - Buttons.NumericUpDown scrollPosition = new() + Buttons.NumericUpDown scrollPosition = new () { Value = scroll.Position, X = Pos.Right (lblPosition) + 1, @@ -218,7 +217,7 @@ public override void Main () editor.Initialized += (s, e) => { - scroll.Size = 40; + scroll.Size = int.Max (app.ContentSize.Height * 2, app.ContentSize.Width * 2); editor.ViewToEdit = view; }; From 57d4f0dfef280adfdc838763dacda4cfad5acdc4 Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 24 May 2024 11:55:45 -0600 Subject: [PATCH 005/128] More autosize related tweaks --- Terminal.Gui/Views/Scroll.cs | 12 ++++++------ UICatalog/Scenarios/ScrollDemo.cs | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index c9c81e55db..a00f61c4ba 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -21,7 +21,7 @@ public Scroll () CanFocus = false; Orientation = Orientation.Vertical; Width = Dim.Auto (DimAutoStyle.Content, 1); - Height = Dim.Auto (DimAutoStyle.Content); + Height = Dim.Auto (DimAutoStyle.Content, 1); _slider = new () { @@ -69,7 +69,7 @@ public int Position get => _position; set { - int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; + int barSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; if (value < 0 || (value > 0 && value + barSize > Size)) { @@ -232,7 +232,7 @@ private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) { MouseEvent me = e.MouseEvent; int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; - int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; + int barSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) @@ -285,13 +285,13 @@ private void SetSliderText () Text = string.Concat ( Enumerable.Repeat ( Glyphs.Stipple.ToString (), - Frame.Width * Frame.Height)); + ContentSize.Width * ContentSize.Height)); _slider.TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; _slider.Text = string.Concat ( Enumerable.Repeat ( Glyphs.ContinuousMeterSegment.ToString (), - _slider.Frame.Width * _slider.Frame.Height)); + _slider.ContentSize.Width * _slider.ContentSize.Height)); } private void AdjustSlider () @@ -318,7 +318,7 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) { MouseEvent me = e.MouseEvent; int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; - int barSize = Orientation == Orientation.Vertical ? Frame.Height : Frame.Width; + int barSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; int offset = _lastLocation > -1 ? location - _lastLocation : 0; if (me.Flags == MouseFlags.Button1Pressed) diff --git a/UICatalog/Scenarios/ScrollDemo.cs b/UICatalog/Scenarios/ScrollDemo.cs index fcfa0012f0..5848614773 100644 --- a/UICatalog/Scenarios/ScrollDemo.cs +++ b/UICatalog/Scenarios/ScrollDemo.cs @@ -92,6 +92,7 @@ public override void Main () { scroll.Orientation = Orientation.Vertical; scroll.X = Pos.AnchorEnd (); + scroll.Y = 0; scroll.Width = scrollWidthHeight.Value; scroll.Height = Dim.Fill (); scroll.Size /= 3; @@ -99,6 +100,7 @@ public override void Main () else { scroll.Orientation = Orientation.Horizontal; + scroll.X = 0; scroll.Y = Pos.AnchorEnd (); scroll.Width = Dim.Fill (); scroll.Height = scrollWidthHeight.Value; @@ -218,7 +220,7 @@ public override void Main () editor.Initialized += (s, e) => { scroll.Size = int.Max (app.ContentSize.Height * 2, app.ContentSize.Width * 2); - editor.ViewToEdit = view; + editor.ViewToEdit = scroll; }; app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags; From 24e4f651c1ce311d9f9bf0c78d48b32a59789d68 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 24 May 2024 19:22:33 +0100 Subject: [PATCH 006/128] Remove unnecessary ClearOnVisibleFalse property. --- Terminal.Gui/Views/Scroll.cs | 1 - UnitTests/Views/ScrollTests.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index a00f61c4ba..32e1dc4f87 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -17,7 +17,6 @@ public class Scroll : View public Scroll () { WantContinuousButtonPressed = true; - //ClearOnVisibleFalse = false; CanFocus = false; Orientation = Orientation.Vertical; Width = Dim.Auto (DimAutoStyle.Content, 1); diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index d4d77ffcca..ff4598f75a 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -186,7 +186,6 @@ public void Constructor_Defaults () { var scroll = new Scroll (); Assert.True (scroll.WantContinuousButtonPressed); - Assert.False (scroll.ClearOnVisibleFalse); Assert.False (scroll.CanFocus); Assert.Equal (Orientation.Vertical, scroll.Orientation); Assert.Equal (0, scroll.Size); From de6d276e38e6bdc6c4fde72196105743902173f2 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 24 May 2024 19:23:42 +0100 Subject: [PATCH 007/128] Remove unneeded comments. --- Terminal.Gui/Views/Scroll.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index 32e1dc4f87..f9094a1692 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -1,12 +1,4 @@ -// -// Scroll.cs: Vertical or horizontal scroll -// -// Author: BDisp -// -// Licensed under the MIT license -// - -namespace Terminal.Gui; +namespace Terminal.Gui; /// /// Represents the "inside part" of a scroll bar, minus the arrows. From 86e0db278c085d922bf461e19add5799ffb05754 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 24 May 2024 19:28:03 +0100 Subject: [PATCH 008/128] Add mouse wheel. --- Terminal.Gui/Views/Scroll.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index f9094a1692..413590ab0e 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -237,6 +237,16 @@ private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) { Position = Math.Min (Position + barSize, Size - barSize); } + else if ((me.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) + || (me.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) + { + Position = Math.Min (Position + 1, Size - barSize); + } + else if ((me.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical) + || (me.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal)) + { + Position = Math.Max (Position - 1, 0); + } } private void Scroll_Removed (object sender, SuperViewChangedEventArgs e) @@ -350,6 +360,16 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) Application.UngrabMouse (); } } + else if ((me.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) + || (me.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) + { + Position = Math.Min (Position + 1, Size - barSize); + } + else if ((me.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical) + || (me.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal)) + { + Position = Math.Max (Position - 1, 0); + } else { return; From 30be0524e48df01d29802fa27ea439d6f6738350 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 24 May 2024 19:28:54 +0100 Subject: [PATCH 009/128] Ensure the Position is valid if the slider is at end. --- Terminal.Gui/Views/Scroll.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index 413590ab0e..faf9bed24e 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -147,6 +147,13 @@ private int GetPositionFromSliderLocation (int location) int barSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; + // Ensure the Position is valid if the slider is at end + if ((Orientation == Orientation.Vertical && location + _slider.Frame.Height == barSize) + || (Orientation == Orientation.Horizontal && location + _slider.Frame.Width == barSize)) + { + return Size - barSize; + } + return Math.Min (location * Size / barSize, Size - barSize); } From 8c617f33f784b408329f52a0fc9363215ed04a1d Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 24 May 2024 19:31:17 +0100 Subject: [PATCH 010/128] Fixes NumericUpDown resize when number of characters grows. --- UICatalog/Scenarios/Buttons.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index 6ba57e02f3..e05ff5bfbc 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -420,7 +420,7 @@ public NumericUpDown () // TODO: Use Dim.Auto for the Width and Height Height = 1; - Width = Dim.Func (() => Digits + 2); // button + 3 for number + button + Width = Dim.Func (() => _number is null ? Digits + 2 : Math.Max (Digits + 2, _number.Text.Length + 2)); // button + 3 for number + button _down = new () { @@ -438,7 +438,7 @@ public NumericUpDown () Text = Value.ToString (), X = Pos.Right (_down), Y = Pos.Top (_down), - Width = Dim.Func (() => Digits), + Width = Dim.Func (() => _number is null ? Digits : Math.Max (Digits, _number.Text.Length)), Height = 1, TextAlignment = TextAlignment.Centered, CanFocus = true From 365f88981c7893ea29231b53fa7a10c69a0ad949 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 24 May 2024 19:53:28 +0100 Subject: [PATCH 011/128] Added more tweaks. --- UICatalog/Scenarios/ScrollDemo.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/UICatalog/Scenarios/ScrollDemo.cs b/UICatalog/Scenarios/ScrollDemo.cs index 5848614773..d3a76a938f 100644 --- a/UICatalog/Scenarios/ScrollDemo.cs +++ b/UICatalog/Scenarios/ScrollDemo.cs @@ -215,7 +215,25 @@ public override void Main () }; view.Add (lblScrollFrame); - scroll.LayoutComplete += (s, e) => lblScrollFrame.Text = $"Scroll Frame: {scroll.Frame.ToString ()}"; + var lblScrollViewport = new Label + { + Y = Pos.Bottom (lblScrollFrame) + }; + view.Add (lblScrollViewport); + + var lblScrollContentSize = new Label + { + Y = Pos.Bottom (lblScrollViewport) + }; + view.Add (lblScrollContentSize); + + + scroll.LayoutComplete += (s, e) => + { + lblScrollFrame.Text = $"Scroll Frame: {scroll.Frame.ToString ()}"; + lblScrollViewport.Text = $"Scroll Viewport: {scroll.Viewport.ToString ()}"; + lblScrollContentSize.Text = $"Scroll ContentSize: {scroll.ContentSize.ToString ()}"; + }; editor.Initialized += (s, e) => { From d5b98a12ffb08019a7be5a93101cc09e6f96ffaf Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 25 May 2024 08:39:24 -0600 Subject: [PATCH 012/128] Suggestions and questions --- .../View/SuperViewChangedEventArgs.cs | 24 ++- Terminal.Gui/View/ViewSubViews.cs | 4 +- Terminal.Gui/Views/Scroll.cs | 139 ++++++++++-------- UICatalog/KeyBindingsDialog.cs | 2 +- UnitTests/View/SubviewTests.cs | 4 +- 5 files changed, 93 insertions(+), 80 deletions(-) diff --git a/Terminal.Gui/View/SuperViewChangedEventArgs.cs b/Terminal.Gui/View/SuperViewChangedEventArgs.cs index 4f25cf47a9..33eb879083 100644 --- a/Terminal.Gui/View/SuperViewChangedEventArgs.cs +++ b/Terminal.Gui/View/SuperViewChangedEventArgs.cs @@ -1,28 +1,26 @@ namespace Terminal.Gui; /// -/// Args for events where the of a is changed (e.g. +/// EventArgs for events where the state of the of a is changing (e.g. /// / events). /// public class SuperViewChangedEventArgs : EventArgs { /// Creates a new instance of the class. - /// - /// - public SuperViewChangedEventArgs (View parent, View child) + /// + /// + public SuperViewChangedEventArgs (View superView, View subView) { - Parent = parent; - Child = child; + SuperView = superView; + SubView = subView; } - // TODO: Child is the wrong name. It should be View. - /// The view that is having it's changed - public View Child { get; } + /// The SubView that is either being added or removed from . + public View SubView { get; } - // TODO: Parent is the wrong name. It should be SuperView. /// - /// The parent. For this is the old parent (new parent now being null). For - /// it is the new parent to whom view now belongs. + /// The SuperView that is changing state. For this is the SuperView is being removed from. For + /// it is the SuperView is being added to. /// - public View Parent { get; } + public View SuperView { get; } } diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 05d332e304..9fccc06350 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -182,7 +182,7 @@ public View GetTopSuperView (View view = null, View superview = null) /// Event where is the subview being added. public virtual void OnAdded (SuperViewChangedEventArgs e) { - View view = e.Child; + View view = e.SubView; view.IsAdded = true; view.OnResizeNeeded (); view.Added?.Invoke (this, e); @@ -192,7 +192,7 @@ public virtual void OnAdded (SuperViewChangedEventArgs e) /// Event args describing the subview being removed. public virtual void OnRemoved (SuperViewChangedEventArgs e) { - View view = e.Child; + View view = e.SubView; view.IsAdded = false; view.Removed?.Invoke (this, e); } diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index faf9bed24e..e16978834d 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -1,7 +1,7 @@ namespace Terminal.Gui; /// -/// Represents the "inside part" of a scroll bar, minus the arrows. +/// Provides a proportional control for scrolling through content. Used within a . /// public class Scroll : View { @@ -39,7 +39,7 @@ public Scroll () private Orientation _orientation; /// - /// Determines the Orientation of the scroll. + /// Gets or sets if the Scroll is oriented vertically or horizontally. /// public Orientation Orientation { @@ -53,7 +53,7 @@ public Orientation Orientation private int _position; /// - /// The position, relative to , to set the scrollbar at. + /// Gets or sets the position of the start of the Scroll slider, relative to . /// public int Position { @@ -74,26 +74,39 @@ public int Position return; } - int oldPos = _position; - _position = value; - OnPositionChanged (oldPos); if (!_wasSliderMouse) { AdjustSlider (); } + + int oldPos = _position; + _position = value; + OnPositionChanged (oldPos); } } - /// This event is raised when the position on the scrollbar has changed. + /// Raised when the has changed. public event EventHandler> PositionChanged; - /// This event is raised when the position on the scrollbar is changing. + /// Raised when the is changing. Set to to prevent the position from being changed. public event EventHandler> PositionChanging; + /// Virtual method called when has changed. Fires . + protected virtual void OnPositionChanged (int oldPos) { PositionChanged?.Invoke (this, new (oldPos, Position)); } + + /// Virtual method called when is changing. Fires , which is cancelable. + protected virtual StateEventArgs OnPositionChanging (int oldPos, int newPos) + { + StateEventArgs args = new (oldPos, newPos); + PositionChanging?.Invoke (this, args); + + return args; + } + private int _size; /// - /// The size of content the scroll represents. + /// Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through. /// public int Size { @@ -107,35 +120,10 @@ public int Size } } - /// This event is raised when the size of the scroll has changed. + /// Raised when has changed. public event EventHandler> SizeChanged; - /// - protected override void Dispose (bool disposing) - { - Added -= Scroll_Added; - Initialized -= Scroll_Initialized; - DrawContent -= Scroll_DrawContent; - MouseEvent -= Scroll_MouseEvent; - _slider.DrawContent -= Scroll_DrawContent; - _slider.MouseEvent -= Slider_MouseEvent; - - base.Dispose (disposing); - } - - /// Virtual method to invoke the event handler. - protected virtual void OnPositionChanged (int oldPos) { PositionChanged?.Invoke (this, new (oldPos, Position)); } - - /// Virtual method to invoke the cancelable event handler. - protected virtual StateEventArgs OnPositionChanging (int oldPos, int newPos) - { - StateEventArgs args = new (oldPos, newPos); - PositionChanging?.Invoke (this, args); - - return args; - } - - /// Virtual method to invoke the event handler. + /// Virtual method called when has changed. Fires . protected void OnSizeChanged (int oldSize) { SizeChanged?.Invoke (this, new (oldSize, Size)); } private int GetPositionFromSliderLocation (int location) @@ -145,18 +133,20 @@ private int GetPositionFromSliderLocation (int location) return 0; } - int barSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; + int scrollSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; // Ensure the Position is valid if the slider is at end - if ((Orientation == Orientation.Vertical && location + _slider.Frame.Height == barSize) - || (Orientation == Orientation.Horizontal && location + _slider.Frame.Width == barSize)) + // We use Frame here instead of ContentSize because even if the slider has a margin or border, Frame indicates the actual size + if ((Orientation == Orientation.Vertical && location + _slider.Frame.Height == scrollSize) + || (Orientation == Orientation.Horizontal && location + _slider.Frame.Width == scrollSize)) { - return Size - barSize; + return Size - scrollSize; } - return Math.Min (location * Size / barSize, Size - barSize); + return Math.Min (location * Size / scrollSize, Size - scrollSize); } + // QUESTION: This method is only called from one place. Should it be inlined? Or, should it be made internal and unit tests be provided? private (int Location, int Dimension) GetSliderLocationDimensionFromPosition () { if (ContentSize.Height == 0 || ContentSize.Width == 0) @@ -164,37 +154,38 @@ private int GetPositionFromSliderLocation (int location) return new (0, 0); } - int barSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; + int scrollSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; int location; int dimension; if (Size > 0) { - dimension = Math.Min (Math.Max (barSize * barSize / Size, 1), barSize); + dimension = Math.Min (Math.Max (scrollSize * scrollSize / Size, 1), scrollSize); // Ensure the Position is valid - if (Position > 0 && Position + barSize > Size) + if (Position > 0 && Position + scrollSize > Size) { - Position = Size - barSize; + Position = Size - scrollSize; } - location = Math.Min (Position * barSize / Size, barSize - dimension); + location = Math.Min (Position * scrollSize / Size, scrollSize - dimension); - if (Position == Size - barSize && location + dimension < barSize) + if (Position == Size - scrollSize && location + dimension < scrollSize) { - location = barSize - dimension; + location = scrollSize - dimension; } } else { location = 0; - dimension = barSize; + dimension = scrollSize; } return new (location, dimension); } - private void Parent_LayoutComplete (object sender, LayoutEventArgs e) + // TODO: This is unnecessary. If Scroll.Width/Height is Dim.Auto, the Superview will get resized automatically. + private void SuperView_LayoutComplete (object sender, LayoutEventArgs e) { if (!_wasSliderMouse) { @@ -206,19 +197,23 @@ private void Parent_LayoutComplete (object sender, LayoutEventArgs e) } } - private void Parent_MouseEnter (object sender, MouseEventEventArgs e) { OnMouseEnter (e.MouseEvent); } + private void SuperView_MouseEnter (object sender, MouseEventEventArgs e) { OnMouseEnter (e.MouseEvent); } - private void Parent_MouseLeave (object sender, MouseEventEventArgs e) { OnMouseLeave (e.MouseEvent); } + private void SuperView_MouseLeave (object sender, MouseEventEventArgs e) { OnMouseLeave (e.MouseEvent); } private void Scroll_Added (object sender, SuperViewChangedEventArgs e) { - View parent = e.Parent is Adornment adornment ? adornment.Parent : e.Parent; + View parent = e.SuperView is Adornment adornment ? adornment.Parent : e.SuperView; + + parent.LayoutComplete += SuperView_LayoutComplete; - parent.LayoutComplete += Parent_LayoutComplete; - parent.MouseEnter += Parent_MouseEnter; - parent.MouseLeave += Parent_MouseLeave; + // QUESTION: I really don't like this. It feels like a hack that a subview needs to track its parent's mouse events. + // QUESTION: Can we figure out a way to do this without tracking the parent's mouse events? + parent.MouseEnter += SuperView_MouseEnter; + parent.MouseLeave += SuperView_MouseLeave; } + // TODO: Just override GetNormalColor instead of having this method (make Slider a View sub-class that overrides GetNormalColor) private void Scroll_DrawContent (object sender, DrawEventArgs e) { SetColorSchemeWithSuperview (sender as View); } private void Scroll_Initialized (object sender, EventArgs e) @@ -226,6 +221,9 @@ private void Scroll_Initialized (object sender, EventArgs e) AdjustSlider (); } + // TODO: I think you should create a new `internal` view named "ScrollSlider" with an `Orientation` property. It should inherit from View and override GetNormalColor and the mouse events + // that can be moved within it's Superview, constrained to move only horizontally or vertically depending on Orientation. + // This will really simplify a lot of this. private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) { MouseEvent me = e.MouseEvent; @@ -258,13 +256,13 @@ private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) private void Scroll_Removed (object sender, SuperViewChangedEventArgs e) { - if (e.Parent is { }) + if (e.SuperView is { }) { - View parent = e.Parent is Adornment adornment ? adornment.Parent : e.Parent; + View parent = e.SuperView is Adornment adornment ? adornment.Parent : e.SuperView; - parent.LayoutComplete -= Parent_LayoutComplete; - parent.MouseEnter -= Parent_MouseEnter; - parent.MouseLeave -= Parent_MouseLeave; + parent.LayoutComplete -= SuperView_LayoutComplete; + parent.MouseEnter -= SuperView_MouseEnter; + parent.MouseLeave -= SuperView_MouseLeave; } } @@ -290,6 +288,7 @@ private void SetSliderText () { TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; + // QUESTION: Should these Glyphs be configurable via CM? Text = string.Concat ( Enumerable.Repeat ( Glyphs.Stipple.ToString (), @@ -318,10 +317,12 @@ private void AdjustSlider () Orientation == Orientation.Vertical ? ContentSize.Width : slider.Dimension, Orientation == Orientation.Vertical ? slider.Dimension : ContentSize.Height )); - SetSliderText (); } + // TODO: Move this into "ScrollSlider" and override it there. Scroll can then subscribe to _slider.LayoutComplete and call AdjustSlider. + // QUESTION: I've been meaning to add a "View.FrameChanged" event (fired from LayoutComplete only if Frame has changed). Should we do that as part of this PR? + // QUESTION: Note I *did* add "View.ViewportChanged" in a previous PR. private void Slider_MouseEvent (object sender, MouseEventEventArgs e) { MouseEvent me = e.MouseEvent; @@ -384,4 +385,18 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) e.Handled = true; } + + /// + protected override void Dispose (bool disposing) + { + Added -= Scroll_Added; + Initialized -= Scroll_Initialized; + DrawContent -= Scroll_DrawContent; + MouseEvent -= Scroll_MouseEvent; + _slider.DrawContent -= Scroll_DrawContent; + _slider.MouseEvent -= Slider_MouseEvent; + + base.Dispose (disposing); + } + } diff --git a/UICatalog/KeyBindingsDialog.cs b/UICatalog/KeyBindingsDialog.cs index 128bff3edc..4e2e760dc4 100644 --- a/UICatalog/KeyBindingsDialog.cs +++ b/UICatalog/KeyBindingsDialog.cs @@ -208,7 +208,7 @@ private void RecordView (View view) // (and always was wrong). Parents don't get to be told when new views are added // to them - view.Added += (s, e) => RecordView (e.Child); + view.Added += (s, e) => RecordView (e.SubView); } } } diff --git a/UnitTests/View/SubviewTests.cs b/UnitTests/View/SubviewTests.cs index 6f9d84f542..8fd38e437c 100644 --- a/UnitTests/View/SubviewTests.cs +++ b/UnitTests/View/SubviewTests.cs @@ -19,13 +19,13 @@ public void Added_Removed () { Assert.Same (v.SuperView, e.Parent); Assert.Same (t, e.Parent); - Assert.Same (v, e.Child); + Assert.Same (v, e.SubView); }; v.Removed += (s, e) => { Assert.Same (t, e.Parent); - Assert.Same (v, e.Child); + Assert.Same (v, e.SubView); Assert.True (v.SuperView == null); }; From ee47e75d0c90d7adbd4ea30b880483118332cae4 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 25 May 2024 16:30:07 +0100 Subject: [PATCH 013/128] Change Parent to SuperView. --- Terminal.Gui/Views/ScrollBarView.cs | 2 +- UnitTests/View/SubviewTests.cs | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index b30a39415f..d336724a4e 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -41,7 +41,7 @@ public ScrollBarView () WantContinuousButtonPressed = true; ClearOnVisibleFalse = false; - Added += (s, e) => CreateBottomRightCorner (e.Parent); + Added += (s, e) => CreateBottomRightCorner (e.SuperView); Initialized += ScrollBarView_Initialized; } diff --git a/UnitTests/View/SubviewTests.cs b/UnitTests/View/SubviewTests.cs index 8fd38e437c..e13cfbb9b4 100644 --- a/UnitTests/View/SubviewTests.cs +++ b/UnitTests/View/SubviewTests.cs @@ -17,14 +17,14 @@ public void Added_Removed () v.Added += (s, e) => { - Assert.Same (v.SuperView, e.Parent); - Assert.Same (t, e.Parent); + Assert.Same (v.SuperView, e.SuperView); + Assert.Same (t, e.SuperView); Assert.Same (v, e.SubView); }; v.Removed += (s, e) => { - Assert.Same (t, e.Parent); + Assert.Same (t, e.SuperView); Assert.Same (v, e.SubView); Assert.True (v.SuperView == null); }; @@ -108,26 +108,26 @@ public void Initialized_Event_Comparing_With_Added_Event () winAddedToTop.Added += (s, e) => { - Assert.Equal (e.Parent.Frame.Width, winAddedToTop.Frame.Width); - Assert.Equal (e.Parent.Frame.Height, winAddedToTop.Frame.Height); + Assert.Equal (e.SuperView.Frame.Width, winAddedToTop.Frame.Width); + Assert.Equal (e.SuperView.Frame.Height, winAddedToTop.Frame.Height); }; v1AddedToWin.Added += (s, e) => { - Assert.Equal (e.Parent.Frame.Width, v1AddedToWin.Frame.Width); - Assert.Equal (e.Parent.Frame.Height, v1AddedToWin.Frame.Height); + Assert.Equal (e.SuperView.Frame.Width, v1AddedToWin.Frame.Width); + Assert.Equal (e.SuperView.Frame.Height, v1AddedToWin.Frame.Height); }; v2AddedToWin.Added += (s, e) => { - Assert.Equal (e.Parent.Frame.Width, v2AddedToWin.Frame.Width); - Assert.Equal (e.Parent.Frame.Height, v2AddedToWin.Frame.Height); + Assert.Equal (e.SuperView.Frame.Width, v2AddedToWin.Frame.Width); + Assert.Equal (e.SuperView.Frame.Height, v2AddedToWin.Frame.Height); }; svAddedTov1.Added += (s, e) => { - Assert.Equal (e.Parent.Frame.Width, svAddedTov1.Frame.Width); - Assert.Equal (e.Parent.Frame.Height, svAddedTov1.Frame.Height); + Assert.Equal (e.SuperView.Frame.Width, svAddedTov1.Frame.Width); + Assert.Equal (e.SuperView.Frame.Height, svAddedTov1.Frame.Height); }; top.Initialized += (s, e) => From 4e57840dce58de049b8a33ce84b1349769dc3dd9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 12 Jun 2024 21:24:22 +0100 Subject: [PATCH 014/128] Fix merge errors. --- Terminal.Gui/Views/Scroll.cs | 22 +++++++++++----------- UICatalog/Scenarios/ScrollDemo.cs | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index e16978834d..f3b06fbe31 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -60,7 +60,7 @@ public int Position get => _position; set { - int barSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; + int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; if (value < 0 || (value > 0 && value + barSize > Size)) { @@ -128,12 +128,12 @@ public int Size private int GetPositionFromSliderLocation (int location) { - if (ContentSize.Height == 0 || ContentSize.Width == 0) + if (GetContentSize ().Height == 0 || GetContentSize ().Width == 0) { return 0; } - int scrollSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; + int scrollSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : 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 @@ -149,12 +149,12 @@ private int GetPositionFromSliderLocation (int location) // QUESTION: This method is only called from one place. Should it be inlined? Or, should it be made internal and unit tests be provided? private (int Location, int Dimension) GetSliderLocationDimensionFromPosition () { - if (ContentSize.Height == 0 || ContentSize.Width == 0) + if (GetContentSize ().Height == 0 || GetContentSize ().Width == 0) { return new (0, 0); } - int scrollSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; + int scrollSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; int location; int dimension; @@ -228,7 +228,7 @@ private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) { MouseEvent me = e.MouseEvent; int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; - int barSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; + int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) @@ -292,13 +292,13 @@ private void SetSliderText () Text = string.Concat ( Enumerable.Repeat ( Glyphs.Stipple.ToString (), - ContentSize.Width * ContentSize.Height)); + GetContentSize ().Width * GetContentSize ().Height)); _slider.TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; _slider.Text = string.Concat ( Enumerable.Repeat ( Glyphs.ContinuousMeterSegment.ToString (), - _slider.ContentSize.Width * _slider.ContentSize.Height)); + _slider.GetContentSize ().Width * _slider.GetContentSize ().Height)); } private void AdjustSlider () @@ -314,8 +314,8 @@ private void AdjustSlider () _slider.SetContentSize ( new ( - Orientation == Orientation.Vertical ? ContentSize.Width : slider.Dimension, - Orientation == Orientation.Vertical ? slider.Dimension : ContentSize.Height + Orientation == Orientation.Vertical ? GetContentSize ().Width : slider.Dimension, + Orientation == Orientation.Vertical ? slider.Dimension : GetContentSize ().Height )); SetSliderText (); } @@ -327,7 +327,7 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) { MouseEvent me = e.MouseEvent; int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; - int barSize = Orientation == Orientation.Vertical ? ContentSize.Height : ContentSize.Width; + int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; int offset = _lastLocation > -1 ? location - _lastLocation : 0; if (me.Flags == MouseFlags.Button1Pressed) diff --git a/UICatalog/Scenarios/ScrollDemo.cs b/UICatalog/Scenarios/ScrollDemo.cs index d3a76a938f..955fa91b20 100644 --- a/UICatalog/Scenarios/ScrollDemo.cs +++ b/UICatalog/Scenarios/ScrollDemo.cs @@ -20,7 +20,7 @@ public override void Main () Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" }; - var editor = new Adornments.AdornmentsEditor (); + var editor = new AdornmentsEditor (); app.Add (editor); var view = new FrameView @@ -232,12 +232,12 @@ public override void Main () { lblScrollFrame.Text = $"Scroll Frame: {scroll.Frame.ToString ()}"; lblScrollViewport.Text = $"Scroll Viewport: {scroll.Viewport.ToString ()}"; - lblScrollContentSize.Text = $"Scroll ContentSize: {scroll.ContentSize.ToString ()}"; + lblScrollContentSize.Text = $"Scroll ContentSize: {scroll.GetContentSize ().ToString ()}"; }; editor.Initialized += (s, e) => { - scroll.Size = int.Max (app.ContentSize.Height * 2, app.ContentSize.Width * 2); + scroll.Size = int.Max (app.GetContentSize ().Height * 2, app.GetContentSize ().Width * 2); editor.ViewToEdit = scroll; }; From 7489d6ca898d3eb71ac3d8df2ed5167503aa260a Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 22 Jun 2024 17:21:09 +0100 Subject: [PATCH 015/128] Replace local var with private getter field. --- Terminal.Gui/Views/Scroll.cs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index f3b06fbe31..900af97448 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -37,6 +37,8 @@ public Scroll () private bool _wasSliderMouse; + private int _barSize => Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; + private Orientation _orientation; /// /// Gets or sets if the Scroll is oriented vertically or horizontally. @@ -60,9 +62,7 @@ public int Position get => _position; set { - int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; - - if (value < 0 || (value > 0 && value + barSize > Size)) + if (value < 0 || (value > 0 && value + _barSize > Size)) { return; } @@ -74,7 +74,6 @@ public int Position return; } - if (!_wasSliderMouse) { AdjustSlider (); @@ -228,7 +227,6 @@ private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) { MouseEvent me = e.MouseEvent; int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; - int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) @@ -236,16 +234,16 @@ private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) if (me.Flags == MouseFlags.Button1Pressed && location < sliderPos.topLeft) { - Position = Math.Max (Position - barSize, 0); + Position = Math.Max (Position - _barSize, 0); } else if (me.Flags == MouseFlags.Button1Pressed && location > sliderPos.bottomRight) { - Position = Math.Min (Position + barSize, Size - barSize); + Position = Math.Min (Position + _barSize, Size - _barSize); } else if ((me.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) || (me.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) { - Position = Math.Min (Position + 1, Size - barSize); + Position = Math.Min (Position + 1, Size - _barSize); } else if ((me.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical) || (me.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal)) @@ -327,7 +325,6 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) { MouseEvent me = e.MouseEvent; int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; - int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; int offset = _lastLocation > -1 ? location - _lastLocation : 0; if (me.Flags == MouseFlags.Button1Pressed) @@ -342,7 +339,7 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) { if (Orientation == Orientation.Vertical) { - if (_slider.Frame.Y + offset >= 0 && _slider.Frame.Y + offset + _slider.Frame.Height <= barSize) + if (_slider.Frame.Y + offset >= 0 && _slider.Frame.Y + offset + _slider.Frame.Height <= _barSize) { _wasSliderMouse = true; _slider.Y = _slider.Frame.Y + offset; @@ -351,7 +348,7 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) } else { - if (_slider.Frame.X + offset >= 0 && _slider.Frame.X + offset + _slider.Frame.Width <= barSize) + if (_slider.Frame.X + offset >= 0 && _slider.Frame.X + offset + _slider.Frame.Width <= _barSize) { _wasSliderMouse = true; _slider.X = _slider.Frame.X + offset; @@ -371,7 +368,7 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) else if ((me.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) || (me.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) { - Position = Math.Min (Position + 1, Size - barSize); + Position = Math.Min (Position + 1, Size - _barSize); } else if ((me.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical) || (me.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal)) From 1e9e27a7f3d3b525a0f37e5b1fd2b7176021a09a Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 22 Jun 2024 18:52:34 +0100 Subject: [PATCH 016/128] Only raises PositionChanging and PositionChanged if position has changed, --- Terminal.Gui/Views/Scroll.cs | 2 +- UnitTests/Views/ScrollTests.cs | 71 ++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index 900af97448..8ecd513316 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -62,7 +62,7 @@ public int Position get => _position; set { - if (value < 0 || (value > 0 && value + _barSize > Size)) + if (value == _position || value < 0 || value + _barSize > Size) { return; } diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index ff4598f75a..19b3d98e84 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -843,4 +843,75 @@ public void Vertical_Horizontal_Draws_Correctly (int sizeWidth, int sizeHeight, _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); } + + [Fact] + public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position_Was_Really_Changed () + { + var changing = 0; + var cancel = false; + var changed = 0; + var scroll = new Scroll { Height = 10, Size = 20 }; + scroll.PositionChanging += Scroll_PositionChanging; + scroll.PositionChanged += Scroll_PositionChanged; + + Assert.Equal (Orientation.Vertical, scroll.Orientation); + Assert.Equal (new (0, 0, 1, 10), scroll.Viewport); + Assert.Equal (0, scroll.Position); + Assert.Equal (0, changing); + Assert.Equal (0, changed); + + scroll.Position = 0; + Assert.Equal (0, scroll.Position); + Assert.Equal (0, changing); + Assert.Equal (0, changed); + + scroll.Position = 1; + Assert.Equal (1, scroll.Position); + Assert.Equal (1, changing); + Assert.Equal (1, changed); + + Reset (); + cancel = true; + scroll.Position = 2; + Assert.Equal (1, scroll.Position); + Assert.Equal (1, changing); + Assert.Equal (0, changed); + + Reset (); + scroll.Position = 10; + Assert.Equal (10, scroll.Position); + Assert.Equal (1, changing); + Assert.Equal (1, changed); + + Reset (); + scroll.Position = 11; + Assert.Equal (10, scroll.Position); + Assert.Equal (0, changing); + Assert.Equal (0, changed); + + Reset (); + scroll.Position = 0; + Assert.Equal (0, scroll.Position); + Assert.Equal (1, changing); + Assert.Equal (1, changed); + + scroll.PositionChanging -= Scroll_PositionChanging; + scroll.PositionChanged -= Scroll_PositionChanged; + + + void Scroll_PositionChanging (object sender, StateEventArgs e) + { + changing++; + e.Cancel = cancel; + } + + void Scroll_PositionChanged (object sender, StateEventArgs e) => changed++; + + void Reset () + { + changing = 0; + cancel = false; + changed = 0; + } + } } From becad1da182ccf3a97ed14f9f084c5a5a3588ef6 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 22 Jun 2024 23:36:47 +0100 Subject: [PATCH 017/128] Add slider highlight effect. --- Terminal.Gui/Views/Scroll.cs | 59 +++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index 8ecd513316..da77285039 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -18,17 +18,18 @@ public Scroll () { Id = "slider", Width = Dim.Auto (DimAutoStyle.Content), - Height = Dim.Auto (DimAutoStyle.Content) + Height = Dim.Auto (DimAutoStyle.Content), + WantMousePositionReports = true }; Add (_slider); Added += Scroll_Added; Removed += Scroll_Removed; Initialized += Scroll_Initialized; - DrawContent += Scroll_DrawContent; MouseEvent += Scroll_MouseEvent; - _slider.DrawContent += Scroll_DrawContent; _slider.MouseEvent += Slider_MouseEvent; + _slider.MouseEnter += Slider_MouseEnter; + _slider.MouseLeave += Slider_MouseLeave; } private readonly View _slider; @@ -210,10 +211,11 @@ private void Scroll_Added (object sender, SuperViewChangedEventArgs e) // QUESTION: Can we figure out a way to do this without tracking the parent's mouse events? parent.MouseEnter += SuperView_MouseEnter; parent.MouseLeave += SuperView_MouseLeave; + + _slider.ColorScheme = new () { Normal = new (parent.ColorScheme.HotNormal.Foreground, parent.ColorScheme.HotNormal.Foreground) }; } // TODO: Just override GetNormalColor instead of having this method (make Slider a View sub-class that overrides GetNormalColor) - private void Scroll_DrawContent (object sender, DrawEventArgs e) { SetColorSchemeWithSuperview (sender as View); } private void Scroll_Initialized (object sender, EventArgs e) { @@ -250,6 +252,13 @@ private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) { Position = Math.Max (Position - 1, 0); } + else if (me.Flags == MouseFlags.Button1Clicked) + { + if (_slider.Frame.Contains (me.Position)) + { + Slider_MouseEnter (_slider, e); + } + } } private void Scroll_Removed (object sender, SuperViewChangedEventArgs e) @@ -264,24 +273,6 @@ private void Scroll_Removed (object sender, SuperViewChangedEventArgs e) } } - // TODO: Just override GetNormalColor instead of having this method - private static void SetColorSchemeWithSuperview (View view) - { - if (view.SuperView is { }) - { - View parent = view.SuperView is Adornment adornment ? adornment.Parent : view.SuperView; - - if (view.Id == "slider") - { - view.ColorScheme = new () { Normal = new (parent.ColorScheme.Normal.Foreground, parent.ColorScheme.Normal.Foreground) }; - } - else - { - view.ColorScheme = parent.ColorScheme; - } - } - } - private void SetSliderText () { TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; @@ -375,7 +366,7 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) { Position = Math.Max (Position - 1, 0); } - else + else if (me.Flags != MouseFlags.ReportMousePosition) { return; } @@ -383,15 +374,33 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) e.Handled = true; } + [CanBeNull] + private ColorScheme _savedColorScheme; + + private void Slider_MouseEnter (object sender, MouseEventEventArgs e) + { + _savedColorScheme ??= _slider.ColorScheme; + _slider.ColorScheme = new () { Normal = new (_savedColorScheme.HotNormal.Foreground, _savedColorScheme.HotNormal.Foreground) }; + } + + private void Slider_MouseLeave (object sender, MouseEventEventArgs e) + { + if (_savedColorScheme is { } && !e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) + { + _slider.ColorScheme = _savedColorScheme; + _savedColorScheme = null; + } + } + /// protected override void Dispose (bool disposing) { Added -= Scroll_Added; Initialized -= Scroll_Initialized; - DrawContent -= Scroll_DrawContent; MouseEvent -= Scroll_MouseEvent; - _slider.DrawContent -= Scroll_DrawContent; _slider.MouseEvent -= Slider_MouseEvent; + _slider.MouseEnter -= Slider_MouseEnter; + _slider.MouseLeave -= Slider_MouseLeave; base.Dispose (disposing); } From 5d2120eac69f2bec85947842b87d78daaa5b26c3 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 23 Jun 2024 12:59:19 +0100 Subject: [PATCH 018/128] Replace private _barSize with local variables barSize and improving performance in the Position property. --- Terminal.Gui/Views/Scroll.cs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index da77285039..0adfd4f906 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -38,8 +38,6 @@ public Scroll () private bool _wasSliderMouse; - private int _barSize => Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; - private Orientation _orientation; /// /// Gets or sets if the Scroll is oriented vertically or horizontally. @@ -63,7 +61,14 @@ public int Position get => _position; set { - if (value == _position || value < 0 || value + _barSize > Size) + if (value == _position || value < 0) + { + return; + } + + int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; + + if (value + barSize > Size) { return; } @@ -229,6 +234,7 @@ private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) { MouseEvent me = e.MouseEvent; int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; + int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) @@ -236,16 +242,16 @@ private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) if (me.Flags == MouseFlags.Button1Pressed && location < sliderPos.topLeft) { - Position = Math.Max (Position - _barSize, 0); + Position = Math.Max (Position - barSize, 0); } else if (me.Flags == MouseFlags.Button1Pressed && location > sliderPos.bottomRight) { - Position = Math.Min (Position + _barSize, Size - _barSize); + Position = Math.Min (Position + barSize, Size - barSize); } else if ((me.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) || (me.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) { - Position = Math.Min (Position + 1, Size - _barSize); + Position = Math.Min (Position + 1, Size - barSize); } else if ((me.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical) || (me.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal)) @@ -317,6 +323,7 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) MouseEvent me = e.MouseEvent; int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; int offset = _lastLocation > -1 ? location - _lastLocation : 0; + int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; if (me.Flags == MouseFlags.Button1Pressed) { @@ -330,7 +337,7 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) { if (Orientation == Orientation.Vertical) { - if (_slider.Frame.Y + offset >= 0 && _slider.Frame.Y + offset + _slider.Frame.Height <= _barSize) + if (_slider.Frame.Y + offset >= 0 && _slider.Frame.Y + offset + _slider.Frame.Height <= barSize) { _wasSliderMouse = true; _slider.Y = _slider.Frame.Y + offset; @@ -339,7 +346,7 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) } else { - if (_slider.Frame.X + offset >= 0 && _slider.Frame.X + offset + _slider.Frame.Width <= _barSize) + if (_slider.Frame.X + offset >= 0 && _slider.Frame.X + offset + _slider.Frame.Width <= barSize) { _wasSliderMouse = true; _slider.X = _slider.Frame.X + offset; @@ -359,7 +366,7 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) else if ((me.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) || (me.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) { - Position = Math.Min (Position + 1, Size - _barSize); + Position = Math.Min (Position + 1, Size - barSize); } else if ((me.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical) || (me.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal)) From 5c6b39b842c8616ad813d1969b743a765d9cd453 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 23 Jun 2024 18:59:13 +0100 Subject: [PATCH 019/128] Removed unnecessary methods and now unit test pass. --- Terminal.Gui/Views/Scroll.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index 0adfd4f906..e8ea545961 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -202,25 +202,30 @@ private void SuperView_LayoutComplete (object sender, LayoutEventArgs e) } } - private void SuperView_MouseEnter (object sender, MouseEventEventArgs e) { OnMouseEnter (e.MouseEvent); } - private void SuperView_MouseLeave (object sender, MouseEventEventArgs e) { OnMouseLeave (e.MouseEvent); } private void Scroll_Added (object sender, SuperViewChangedEventArgs e) { View parent = e.SuperView is Adornment adornment ? adornment.Parent : e.SuperView; parent.LayoutComplete += SuperView_LayoutComplete; + } - // QUESTION: I really don't like this. It feels like a hack that a subview needs to track its parent's mouse events. - // QUESTION: Can we figure out a way to do this without tracking the parent's mouse events? - parent.MouseEnter += SuperView_MouseEnter; - parent.MouseLeave += SuperView_MouseLeave; - _slider.ColorScheme = new () { Normal = new (parent.ColorScheme.HotNormal.Foreground, parent.ColorScheme.HotNormal.Foreground) }; - } + /// + public override Attribute GetNormalColor () + { + if (_savedColorScheme is null) + { + _slider.ColorScheme = new () { Normal = new (ColorScheme.HotNormal.Foreground, ColorScheme.HotNormal.Foreground) }; + } + else + { + _slider.ColorScheme = new () { Normal = new (ColorScheme.Normal.Foreground, ColorScheme.Normal.Foreground) }; + } - // TODO: Just override GetNormalColor instead of having this method (make Slider a View sub-class that overrides GetNormalColor) + return base.GetNormalColor (); + } private void Scroll_Initialized (object sender, EventArgs e) { @@ -274,8 +279,6 @@ private void Scroll_Removed (object sender, SuperViewChangedEventArgs e) View parent = e.SuperView is Adornment adornment ? adornment.Parent : e.SuperView; parent.LayoutComplete -= SuperView_LayoutComplete; - parent.MouseEnter -= SuperView_MouseEnter; - parent.MouseLeave -= SuperView_MouseLeave; } } @@ -411,5 +414,4 @@ protected override void Dispose (bool disposing) base.Dispose (disposing); } - } From d44efe39808af13ea29cecaed11859236663574a Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 24 Jun 2024 11:40:04 +0100 Subject: [PATCH 020/128] Setting entire ColorScheme attributes. --- Terminal.Gui/Views/Scroll.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index e8ea545961..9ce5b14e18 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -390,7 +390,14 @@ private void Slider_MouseEvent (object sender, MouseEventEventArgs e) private void Slider_MouseEnter (object sender, MouseEventEventArgs e) { _savedColorScheme ??= _slider.ColorScheme; - _slider.ColorScheme = new () { Normal = new (_savedColorScheme.HotNormal.Foreground, _savedColorScheme.HotNormal.Foreground) }; + _slider.ColorScheme = new () + { + Normal = new (_savedColorScheme.HotNormal.Foreground, _savedColorScheme.HotNormal.Foreground), + Focus = new (_savedColorScheme.Focus.Foreground, _savedColorScheme.Focus.Foreground), + HotNormal = new (_savedColorScheme.Normal.Foreground, _savedColorScheme.Normal.Foreground), + HotFocus = new (_savedColorScheme.HotFocus.Foreground, _savedColorScheme.HotFocus.Foreground), + Disabled = new (_savedColorScheme.Disabled.Foreground, _savedColorScheme.Disabled.Foreground) + }; } private void Slider_MouseLeave (object sender, MouseEventEventArgs e) From f9aa6191625f2712c8757d132f58e28d117105c1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 11 Jul 2024 12:22:02 +0100 Subject: [PATCH 021/128] Fix merge errors. --- Terminal.Gui/Views/Scroll.cs | 34 +++++++++++++++---------------- UICatalog/Scenarios/ScrollDemo.cs | 12 +++++------ UnitTests/Views/ScrollTests.cs | 4 ++-- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index 9ce5b14e18..3aa9f0e6b9 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -1,4 +1,6 @@ -namespace Terminal.Gui; +using System.ComponentModel; + +namespace Terminal.Gui; /// /// Provides a proportional control for scrolling through content. Used within a . @@ -73,7 +75,7 @@ public int Position return; } - StateEventArgs args = OnPositionChanging (_position, value); + CancelEventArgs args = OnPositionChanging (_position, value); if (args.Cancel) { @@ -85,25 +87,24 @@ public int Position AdjustSlider (); } - int oldPos = _position; _position = value; - OnPositionChanged (oldPos); + OnPositionChanged (_position); } } /// Raised when the has changed. - public event EventHandler> PositionChanged; + public event EventHandler> PositionChanged; - /// Raised when the is changing. Set to to prevent the position from being changed. - public event EventHandler> PositionChanging; + /// Raised when the is changing. Set to to prevent the position from being changed. + public event EventHandler> PositionChanging; - /// Virtual method called when has changed. Fires . - protected virtual void OnPositionChanged (int oldPos) { PositionChanged?.Invoke (this, new (oldPos, Position)); } + /// Virtual method called when has changed. Raises . + protected virtual void OnPositionChanged (int position) { PositionChanged?.Invoke (this, new (ref position)); } - /// Virtual method called when is changing. Fires , which is cancelable. - protected virtual StateEventArgs OnPositionChanging (int oldPos, int newPos) + /// Virtual method called when is changing. Raises , which is cancelable. + protected virtual CancelEventArgs OnPositionChanging (int currentPos, int newPos) { - StateEventArgs args = new (oldPos, newPos); + CancelEventArgs args = new (ref currentPos, ref newPos); PositionChanging?.Invoke (this, args); return args; @@ -118,18 +119,17 @@ public int Size get => _size; set { - int oldSize = _size; _size = value; - OnSizeChanged (oldSize); + OnSizeChanged (_size); AdjustSlider (); } } /// Raised when has changed. - public event EventHandler> SizeChanged; + public event EventHandler> SizeChanged; - /// Virtual method called when has changed. Fires . - protected void OnSizeChanged (int oldSize) { SizeChanged?.Invoke (this, new (oldSize, Size)); } + /// Virtual method called when has changed. Raises . + protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (ref size)); } private int GetPositionFromSliderLocation (int location) { diff --git a/UICatalog/Scenarios/ScrollDemo.cs b/UICatalog/Scenarios/ScrollDemo.cs index 955fa91b20..6412f61125 100644 --- a/UICatalog/Scenarios/ScrollDemo.cs +++ b/UICatalog/Scenarios/ScrollDemo.cs @@ -181,11 +181,11 @@ public override void Main () scroll.SizeChanged += (s, e) => { - lblSizeChanged.Text = $"SizeChanged event - OldValue: {e.OldValue}; NewValue: {e.NewValue}"; + lblSizeChanged.Text = $"SizeChanged event - CurrentValue: {e.CurrentValue}"; - if (scrollSize.Value != e.NewValue) + if (scrollSize.Value != e.CurrentValue) { - scrollSize.Value = e.NewValue; + scrollSize.Value = e.CurrentValue; } }; @@ -195,7 +195,7 @@ public override void Main () }; view.Add (lblPosChanging); - scroll.PositionChanging += (s, e) => { lblPosChanging.Text = $"PositionChanging event - OldValue: {e.OldValue}; NewValue: {e.NewValue}"; }; + scroll.PositionChanging += (s, e) => { lblPosChanging.Text = $"PositionChanging event - CurrentValue: {e.CurrentValue}; NewValue: {e.NewValue}"; }; var lblPositionChanged = new Label { @@ -205,8 +205,8 @@ public override void Main () scroll.PositionChanged += (s, e) => { - lblPositionChanged.Text = $"PositionChanged event - OldValue: {e.OldValue}; NewValue: {e.NewValue}"; - scrollPosition.Value = e.NewValue; + lblPositionChanged.Text = $"PositionChanged event - CurrentValue: {e.CurrentValue}"; + scrollPosition.Value = e.CurrentValue; }; var lblScrollFrame = new Label diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index 19b3d98e84..f39091c264 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -899,13 +899,13 @@ public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position scroll.PositionChanged -= Scroll_PositionChanged; - void Scroll_PositionChanging (object sender, StateEventArgs e) + void Scroll_PositionChanging (object sender, CancelEventArgs e) { changing++; e.Cancel = cancel; } - void Scroll_PositionChanged (object sender, StateEventArgs e) => changed++; + void Scroll_PositionChanged (object sender, EventArgs e) => changed++; void Reset () { From 9b4269e9aed88abe886bdb3b60e4b1588496562c Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 14 Aug 2024 14:54:18 +0100 Subject: [PATCH 022/128] Fix merge errors. --- .../View/SuperViewChangedEventArgs.cs | 24 +++++++++-------- Terminal.Gui/Views/Scroll.cs | 10 ++++--- Terminal.Gui/Views/ScrollBarView.cs | 2 +- UICatalog/KeyBindingsDialog.cs | 2 +- UICatalog/Scenarios/ScrollDemo.cs | 6 ++--- UnitTests/View/SubviewTests.cs | 26 +++++++++---------- 6 files changed, 37 insertions(+), 33 deletions(-) diff --git a/Terminal.Gui/View/SuperViewChangedEventArgs.cs b/Terminal.Gui/View/SuperViewChangedEventArgs.cs index 33eb879083..4f25cf47a9 100644 --- a/Terminal.Gui/View/SuperViewChangedEventArgs.cs +++ b/Terminal.Gui/View/SuperViewChangedEventArgs.cs @@ -1,26 +1,28 @@ namespace Terminal.Gui; /// -/// EventArgs for events where the state of the of a is changing (e.g. +/// Args for events where the of a is changed (e.g. /// / events). /// public class SuperViewChangedEventArgs : EventArgs { /// Creates a new instance of the class. - /// - /// - public SuperViewChangedEventArgs (View superView, View subView) + /// + /// + public SuperViewChangedEventArgs (View parent, View child) { - SuperView = superView; - SubView = subView; + Parent = parent; + Child = child; } - /// The SubView that is either being added or removed from . - public View SubView { get; } + // TODO: Child is the wrong name. It should be View. + /// The view that is having it's changed + public View Child { get; } + // TODO: Parent is the wrong name. It should be SuperView. /// - /// The SuperView that is changing state. For this is the SuperView is being removed from. For - /// it is the SuperView is being added to. + /// The parent. For this is the old parent (new parent now being null). For + /// it is the new parent to whom view now belongs. /// - public View SuperView { get; } + public View Parent { get; } } diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index 3aa9f0e6b9..7c734b48af 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -1,4 +1,6 @@ -using System.ComponentModel; +#nullable enable + +using System.ComponentModel; namespace Terminal.Gui; @@ -206,7 +208,7 @@ private void SuperView_LayoutComplete (object sender, LayoutEventArgs e) private void Scroll_Added (object sender, SuperViewChangedEventArgs e) { - View parent = e.SuperView is Adornment adornment ? adornment.Parent : e.SuperView; + View parent = e.Parent is Adornment adornment ? adornment.Parent : e.Parent; parent.LayoutComplete += SuperView_LayoutComplete; } @@ -274,9 +276,9 @@ private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) private void Scroll_Removed (object sender, SuperViewChangedEventArgs e) { - if (e.SuperView is { }) + if (e.Parent is { }) { - View parent = e.SuperView is Adornment adornment ? adornment.Parent : e.SuperView; + View parent = e.Parent is Adornment adornment ? adornment.Parent : e.Parent; parent.LayoutComplete -= SuperView_LayoutComplete; } diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 9ef64407f5..e8b5e487c8 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -40,7 +40,7 @@ public ScrollBarView () WantContinuousButtonPressed = true; ClearOnVisibleFalse = false; - Added += (s, e) => CreateBottomRightCorner (e.SuperView); + Added += (s, e) => CreateBottomRightCorner (e.Parent); Initialized += ScrollBarView_Initialized; } diff --git a/UICatalog/KeyBindingsDialog.cs b/UICatalog/KeyBindingsDialog.cs index 7fe055a98f..4fbfad24a2 100644 --- a/UICatalog/KeyBindingsDialog.cs +++ b/UICatalog/KeyBindingsDialog.cs @@ -209,7 +209,7 @@ private void RecordView (View view) // (and always was wrong). Parents don't get to be told when new views are added // to them - view.Added += (s, e) => RecordView (e.SubView); + view.Added += (s, e) => RecordView (e.Child); } } } diff --git a/UICatalog/Scenarios/ScrollDemo.cs b/UICatalog/Scenarios/ScrollDemo.cs index 6412f61125..c86a1b2996 100644 --- a/UICatalog/Scenarios/ScrollDemo.cs +++ b/UICatalog/Scenarios/ScrollDemo.cs @@ -46,7 +46,7 @@ public override void Main () }; view.Add (lblWidthHeight); - Buttons.NumericUpDown scrollWidthHeight = new () + NumericUpDown scrollWidthHeight = new () { Value = scroll.Frame.Width, X = Pos.Right (lblWidthHeight) + 1, @@ -115,7 +115,7 @@ public override void Main () }; view.Add (lblSize); - Buttons.NumericUpDown scrollSize = new () + NumericUpDown scrollSize = new () { Value = scroll.Size, X = Pos.Right (lblSize) + 1, @@ -145,7 +145,7 @@ public override void Main () }; view.Add (lblPosition); - Buttons.NumericUpDown scrollPosition = new () + NumericUpDown scrollPosition = new () { Value = scroll.Position, X = Pos.Right (lblPosition) + 1, diff --git a/UnitTests/View/SubviewTests.cs b/UnitTests/View/SubviewTests.cs index d588b832d6..c55afe1046 100644 --- a/UnitTests/View/SubviewTests.cs +++ b/UnitTests/View/SubviewTests.cs @@ -17,15 +17,15 @@ public void Added_Removed () v.Added += (s, e) => { - Assert.Same (v.SuperView, e.SuperView); - Assert.Same (t, e.SuperView); - Assert.Same (v, e.SubView); + Assert.Same (v.SuperView, e.Parent); + Assert.Same (t, e.Parent); + Assert.Same (v, e.Child); }; v.Removed += (s, e) => { - Assert.Same (t, e.SuperView); - Assert.Same (v, e.SubView); + Assert.Same (t, e.Parent); + Assert.Same (v, e.Child); Assert.True (v.SuperView == null); }; @@ -108,26 +108,26 @@ public void Initialized_Event_Comparing_With_Added_Event () winAddedToTop.Added += (s, e) => { - Assert.Equal (e.SuperView.Frame.Width, winAddedToTop.Frame.Width); - Assert.Equal (e.SuperView.Frame.Height, winAddedToTop.Frame.Height); + Assert.Equal (e.Parent.Frame.Width, winAddedToTop.Frame.Width); + Assert.Equal (e.Parent.Frame.Height, winAddedToTop.Frame.Height); }; v1AddedToWin.Added += (s, e) => { - Assert.Equal (e.SuperView.Frame.Width, v1AddedToWin.Frame.Width); - Assert.Equal (e.SuperView.Frame.Height, v1AddedToWin.Frame.Height); + Assert.Equal (e.Parent.Frame.Width, v1AddedToWin.Frame.Width); + Assert.Equal (e.Parent.Frame.Height, v1AddedToWin.Frame.Height); }; v2AddedToWin.Added += (s, e) => { - Assert.Equal (e.SuperView.Frame.Width, v2AddedToWin.Frame.Width); - Assert.Equal (e.SuperView.Frame.Height, v2AddedToWin.Frame.Height); + Assert.Equal (e.Parent.Frame.Width, v2AddedToWin.Frame.Width); + Assert.Equal (e.Parent.Frame.Height, v2AddedToWin.Frame.Height); }; svAddedTov1.Added += (s, e) => { - Assert.Equal (e.SuperView.Frame.Width, svAddedTov1.Frame.Width); - Assert.Equal (e.SuperView.Frame.Height, svAddedTov1.Frame.Height); + Assert.Equal (e.Parent.Frame.Width, svAddedTov1.Frame.Width); + Assert.Equal (e.Parent.Frame.Height, svAddedTov1.Frame.Height); }; top.Initialized += (s, e) => From 91e4abf367afa882fd0af468ab4a4ce0e81831af Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 14 Aug 2024 21:01:02 +0100 Subject: [PATCH 023/128] Ensures Position set before call AdjustSlider and --- Terminal.Gui/Views/Scroll.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll.cs index 7c734b48af..d54bea84bf 100644 --- a/Terminal.Gui/Views/Scroll.cs +++ b/Terminal.Gui/Views/Scroll.cs @@ -52,7 +52,7 @@ public Orientation Orientation set { _orientation = value; - AdjustSlider(); + AdjustSlider (); } } @@ -84,12 +84,13 @@ public int Position return; } + _position = value; + if (!_wasSliderMouse) { AdjustSlider (); } - _position = value; OnPositionChanged (_position); } } @@ -150,7 +151,7 @@ private int GetPositionFromSliderLocation (int location) return Size - scrollSize; } - return Math.Min (location * Size / scrollSize, Size - scrollSize); + return Math.Min ((location * Size + location) / scrollSize, Size - scrollSize); } // QUESTION: This method is only called from one place. Should it be inlined? Or, should it be made internal and unit tests be provided? @@ -175,7 +176,7 @@ private int GetPositionFromSliderLocation (int location) Position = Size - scrollSize; } - location = Math.Min (Position * scrollSize / Size, scrollSize - dimension); + location = Math.Min ((Position * scrollSize + Position) / Size, scrollSize - dimension); if (Position == Size - scrollSize && location + dimension < scrollSize) { From b488796562c5f7789eb17569bccd89f6b602ba06 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 14 Aug 2024 21:19:00 +0100 Subject: [PATCH 024/128] Move Scroll.cs to the Scroll folder. --- Terminal.Gui/Views/{ => Scroll}/Scroll.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Terminal.Gui/Views/{ => Scroll}/Scroll.cs (100%) diff --git a/Terminal.Gui/Views/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs similarity index 100% rename from Terminal.Gui/Views/Scroll.cs rename to Terminal.Gui/Views/Scroll/Scroll.cs From fa6fb11197fb21764d61fb0fc911de4770024895 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 14 Aug 2024 23:03:53 +0100 Subject: [PATCH 025/128] Add internal ScrollSlider class. --- Terminal.Gui/Views/Scroll/Scroll.cs | 278 ++++++---------------- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 148 ++++++++++++ 2 files changed, 219 insertions(+), 207 deletions(-) create mode 100644 Terminal.Gui/Views/Scroll/ScrollSlider.cs diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index d54bea84bf..3323354527 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -18,31 +18,23 @@ public Scroll () Width = Dim.Auto (DimAutoStyle.Content, 1); Height = Dim.Auto (DimAutoStyle.Content, 1); - _slider = new () - { - Id = "slider", - Width = Dim.Auto (DimAutoStyle.Content), - Height = Dim.Auto (DimAutoStyle.Content), - WantMousePositionReports = true - }; + _slider = new (this); Add (_slider); - Added += Scroll_Added; - Removed += Scroll_Removed; - Initialized += Scroll_Initialized; - MouseEvent += Scroll_MouseEvent; - _slider.MouseEvent += Slider_MouseEvent; - _slider.MouseEnter += Slider_MouseEnter; - _slider.MouseLeave += Slider_MouseLeave; + Added += Scroll_Added!; + Removed += Scroll_Removed!; + Initialized += Scroll_Initialized!; + MouseEvent += Scroll_MouseEvent!; } - private readonly View _slider; + private readonly ScrollSlider _slider; - private int _lastLocation = -1; + private Orientation _orientation; - private bool _wasSliderMouse; + private int _position; + + private int _size; - private Orientation _orientation; /// /// Gets or sets if the Scroll is oriented vertically or horizontally. /// @@ -56,7 +48,6 @@ public Orientation Orientation } } - private int _position; /// /// Gets or sets the position of the start of the Scroll slider, relative to . /// @@ -86,7 +77,7 @@ public int Position _position = value; - if (!_wasSliderMouse) + if (!_slider._wasSliderMouse) { AdjustSlider (); } @@ -96,24 +87,14 @@ public int Position } /// Raised when the has changed. - public event EventHandler> PositionChanged; - - /// Raised when the is changing. Set to to prevent the position from being changed. - public event EventHandler> PositionChanging; - - /// Virtual method called when has changed. Raises . - protected virtual void OnPositionChanged (int position) { PositionChanged?.Invoke (this, new (ref position)); } + public event EventHandler>? PositionChanged; - /// Virtual method called when is changing. Raises , which is cancelable. - protected virtual CancelEventArgs OnPositionChanging (int currentPos, int newPos) - { - CancelEventArgs args = new (ref currentPos, ref newPos); - PositionChanging?.Invoke (this, args); - - return args; - } + /// + /// Raised when the is changing. Set to + /// to prevent the position from being changed. + /// + public event EventHandler>? PositionChanging; - private int _size; /// /// Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through. /// @@ -129,29 +110,56 @@ public int Size } /// Raised when has changed. - public event EventHandler> SizeChanged; + public event EventHandler>? SizeChanged; - /// Virtual method called when has changed. Raises . - protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (ref size)); } + // TODO: Move this into "ScrollSlider" and override it there. Scroll can then subscribe to _slider.LayoutComplete and call AdjustSlider. + // QUESTION: I've been meaning to add a "View.FrameChanged" event (fired from LayoutComplete only if Frame has changed). Should we do that as part of this PR? + // QUESTION: Note I *did* add "View.ViewportChanged" in a previous PR. + /// + protected override void Dispose (bool disposing) + { + Added -= Scroll_Added!; + Initialized -= Scroll_Initialized!; + MouseEvent -= Scroll_MouseEvent!; + + base.Dispose (disposing); + } - private int GetPositionFromSliderLocation (int location) + /// Virtual method called when has changed. Raises . + protected virtual void OnPositionChanged (int position) { PositionChanged?.Invoke (this, new (in position)); } + + /// + /// Virtual method called when is changing. Raises , which is + /// cancelable. + /// + protected virtual CancelEventArgs OnPositionChanging (int currentPos, int newPos) { - if (GetContentSize ().Height == 0 || GetContentSize ().Width == 0) - { - return 0; - } + CancelEventArgs args = new (ref currentPos, ref newPos); + PositionChanging?.Invoke (this, args); - int scrollSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; + return args; + } + + /// Virtual method called when has changed. Raises . + protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (in size)); } - // 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 - if ((Orientation == Orientation.Vertical && location + _slider.Frame.Height == scrollSize) - || (Orientation == Orientation.Horizontal && location + _slider.Frame.Width == scrollSize)) + private void AdjustSlider () + { + if (!IsInitialized) { - return Size - scrollSize; + return; } - return Math.Min ((location * Size + location) / scrollSize, Size - scrollSize); + (int Location, int Dimension) slider = GetSliderLocationDimensionFromPosition (); + _slider.X = Orientation == Orientation.Vertical ? 0 : slider.Location; + _slider.Y = Orientation == Orientation.Vertical ? slider.Location : 0; + + _slider.SetContentSize ( + new ( + Orientation == Orientation.Vertical ? GetContentSize ().Width : slider.Dimension, + Orientation == Orientation.Vertical ? slider.Dimension : GetContentSize ().Height + )); + SetSliderText (); } // QUESTION: This method is only called from one place. Should it be inlined? Or, should it be made internal and unit tests be provided? @@ -192,48 +200,14 @@ private int GetPositionFromSliderLocation (int location) return new (location, dimension); } - // TODO: This is unnecessary. If Scroll.Width/Height is Dim.Auto, the Superview will get resized automatically. - private void SuperView_LayoutComplete (object sender, LayoutEventArgs e) - { - if (!_wasSliderMouse) - { - AdjustSlider (); - } - else - { - _wasSliderMouse = false; - } - } - - - private void Scroll_Added (object sender, SuperViewChangedEventArgs e) { - View parent = e.Parent is Adornment adornment ? adornment.Parent : e.Parent; - - parent.LayoutComplete += SuperView_LayoutComplete; - } - - - /// - public override Attribute GetNormalColor () - { - if (_savedColorScheme is null) - { - _slider.ColorScheme = new () { Normal = new (ColorScheme.HotNormal.Foreground, ColorScheme.HotNormal.Foreground) }; - } - else - { - _slider.ColorScheme = new () { Normal = new (ColorScheme.Normal.Foreground, ColorScheme.Normal.Foreground) }; - } + View parent = (e.Parent is Adornment adornment ? adornment.Parent : e.Parent)!; - return base.GetNormalColor (); + parent.LayoutComplete += SuperView_LayoutComplete!; } - private void Scroll_Initialized (object sender, EventArgs e) - { - AdjustSlider (); - } + private void Scroll_Initialized (object sender, EventArgs e) { AdjustSlider (); } // TODO: I think you should create a new `internal` view named "ScrollSlider" with an `Orientation` property. It should inherit from View and override GetNormalColor and the mouse events // that can be moved within it's Superview, constrained to move only horizontally or vertically depending on Orientation. @@ -270,7 +244,7 @@ private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) { if (_slider.Frame.Contains (me.Position)) { - Slider_MouseEnter (_slider, e); + _slider.OnMouseEvent (e.MouseEvent); } } } @@ -279,9 +253,9 @@ private void Scroll_Removed (object sender, SuperViewChangedEventArgs e) { if (e.Parent is { }) { - View parent = e.Parent is Adornment adornment ? adornment.Parent : e.Parent; + View parent = (e.Parent is Adornment adornment ? adornment.Parent : e.Parent)!; - parent.LayoutComplete -= SuperView_LayoutComplete; + parent.LayoutComplete -= SuperView_LayoutComplete!; } } @@ -302,126 +276,16 @@ private void SetSliderText () _slider.GetContentSize ().Width * _slider.GetContentSize ().Height)); } - private void AdjustSlider () - { - if (!IsInitialized) - { - return; - } - - (int Location, int Dimension) slider = GetSliderLocationDimensionFromPosition (); - _slider.X = Orientation == Orientation.Vertical ? 0 : slider.Location; - _slider.Y = Orientation == Orientation.Vertical ? slider.Location : 0; - - _slider.SetContentSize ( - new ( - Orientation == Orientation.Vertical ? GetContentSize ().Width : slider.Dimension, - Orientation == Orientation.Vertical ? slider.Dimension : GetContentSize ().Height - )); - SetSliderText (); - } - - // TODO: Move this into "ScrollSlider" and override it there. Scroll can then subscribe to _slider.LayoutComplete and call AdjustSlider. - // QUESTION: I've been meaning to add a "View.FrameChanged" event (fired from LayoutComplete only if Frame has changed). Should we do that as part of this PR? - // QUESTION: Note I *did* add "View.ViewportChanged" in a previous PR. - private void Slider_MouseEvent (object sender, MouseEventEventArgs e) + // TODO: This is unnecessary. If Scroll.Width/Height is Dim.Auto, the Superview will get resized automatically. + private void SuperView_LayoutComplete (object sender, LayoutEventArgs e) { - MouseEvent me = e.MouseEvent; - int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; - int offset = _lastLocation > -1 ? location - _lastLocation : 0; - int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; - - if (me.Flags == MouseFlags.Button1Pressed) - { - if (Application.MouseGrabView != sender as View) - { - Application.GrabMouse (sender as View); - _lastLocation = location; - } - } - else if (me.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) - { - if (Orientation == Orientation.Vertical) - { - if (_slider.Frame.Y + offset >= 0 && _slider.Frame.Y + offset + _slider.Frame.Height <= barSize) - { - _wasSliderMouse = true; - _slider.Y = _slider.Frame.Y + offset; - Position = GetPositionFromSliderLocation (_slider.Frame.Y); - } - } - else - { - if (_slider.Frame.X + offset >= 0 && _slider.Frame.X + offset + _slider.Frame.Width <= barSize) - { - _wasSliderMouse = true; - _slider.X = _slider.Frame.X + offset; - Position = GetPositionFromSliderLocation (_slider.Frame.X); - } - } - } - else if (me.Flags == MouseFlags.Button1Released) - { - _lastLocation = -1; - - if (Application.MouseGrabView == sender as View) - { - Application.UngrabMouse (); - } - } - else if ((me.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) - || (me.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) - { - Position = Math.Min (Position + 1, Size - barSize); - } - else if ((me.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical) - || (me.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal)) - { - Position = Math.Max (Position - 1, 0); - } - else if (me.Flags != MouseFlags.ReportMousePosition) + if (!_slider._wasSliderMouse) { - return; + AdjustSlider (); } - - e.Handled = true; - } - - [CanBeNull] - private ColorScheme _savedColorScheme; - - private void Slider_MouseEnter (object sender, MouseEventEventArgs e) - { - _savedColorScheme ??= _slider.ColorScheme; - _slider.ColorScheme = new () - { - Normal = new (_savedColorScheme.HotNormal.Foreground, _savedColorScheme.HotNormal.Foreground), - Focus = new (_savedColorScheme.Focus.Foreground, _savedColorScheme.Focus.Foreground), - HotNormal = new (_savedColorScheme.Normal.Foreground, _savedColorScheme.Normal.Foreground), - HotFocus = new (_savedColorScheme.HotFocus.Foreground, _savedColorScheme.HotFocus.Foreground), - Disabled = new (_savedColorScheme.Disabled.Foreground, _savedColorScheme.Disabled.Foreground) - }; - } - - private void Slider_MouseLeave (object sender, MouseEventEventArgs e) - { - if (_savedColorScheme is { } && !e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) + else { - _slider.ColorScheme = _savedColorScheme; - _savedColorScheme = null; + _slider._wasSliderMouse = false; } } - - /// - protected override void Dispose (bool disposing) - { - Added -= Scroll_Added; - Initialized -= Scroll_Initialized; - MouseEvent -= Scroll_MouseEvent; - _slider.MouseEvent -= Slider_MouseEvent; - _slider.MouseEnter -= Slider_MouseEnter; - _slider.MouseLeave -= Slider_MouseLeave; - - base.Dispose (disposing); - } } diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs new file mode 100644 index 0000000000..106b76f905 --- /dev/null +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -0,0 +1,148 @@ +#nullable enable + +namespace Terminal.Gui; + +internal class ScrollSlider : View +{ + public ScrollSlider (Scroll host) + { + _host = host; + Id = "slider"; + Width = Dim.Auto (DimAutoStyle.Content); + Height = Dim.Auto (DimAutoStyle.Content); + WantMousePositionReports = true; + } + + internal bool _wasSliderMouse; + + private readonly Scroll _host; + private int _lastLocation = -1; + private ColorScheme? _savedColorScheme; + + /// + public override Attribute GetNormalColor () + { + if (_savedColorScheme is null) + { + ColorScheme = new () { Normal = new (_host.ColorScheme.HotNormal.Foreground, _host.ColorScheme.HotNormal.Foreground) }; + } + else + { + ColorScheme = new () { Normal = new (_host.ColorScheme.Normal.Foreground, _host.ColorScheme.Normal.Foreground) }; + } + + return base.GetNormalColor (); + } + + /// + protected internal override bool? OnMouseEnter (MouseEvent mouseEvent) + { + _savedColorScheme ??= _host.ColorScheme; + + ColorScheme = new () + { + Normal = new (_savedColorScheme.HotNormal.Foreground, _savedColorScheme.HotNormal.Foreground), + Focus = new (_savedColorScheme.Focus.Foreground, _savedColorScheme.Focus.Foreground), + HotNormal = new (_savedColorScheme.Normal.Foreground, _savedColorScheme.Normal.Foreground), + HotFocus = new (_savedColorScheme.HotFocus.Foreground, _savedColorScheme.HotFocus.Foreground), + Disabled = new (_savedColorScheme.Disabled.Foreground, _savedColorScheme.Disabled.Foreground) + }; + + return base.OnMouseEnter (mouseEvent); + } + + /// + protected internal override bool OnMouseEvent (MouseEvent mouseEvent) + { + int location = _host.Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; + int offset = _lastLocation > -1 ? location - _lastLocation : 0; + int barSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width; + + if (mouseEvent.Flags == MouseFlags.Button1Pressed) + { + if (Application.MouseGrabView != this) + { + Application.GrabMouse (this); + _lastLocation = location; + } + } + else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) + { + if (_host.Orientation == Orientation.Vertical) + { + if (Frame.Y + offset >= 0 && Frame.Y + offset + Frame.Height <= barSize) + { + _wasSliderMouse = true; + Y = Frame.Y + offset; + _host.Position = GetPositionFromSliderLocation (Frame.Y); + } + } + else + { + if (Frame.X + offset >= 0 && Frame.X + offset + Frame.Width <= barSize) + { + _wasSliderMouse = true; + X = Frame.X + offset; + _host.Position = GetPositionFromSliderLocation (Frame.X); + } + } + } + else if (mouseEvent.Flags == MouseFlags.Button1Released) + { + _lastLocation = -1; + + if (Application.MouseGrabView == this) + { + Application.UngrabMouse (); + } + } + else if ((mouseEvent.Flags == MouseFlags.WheeledDown && _host.Orientation == Orientation.Vertical) + || (mouseEvent.Flags == MouseFlags.WheeledRight && _host.Orientation == Orientation.Horizontal)) + { + _host.Position = Math.Min (_host.Position + 1, _host.Size - barSize); + } + else if ((mouseEvent.Flags == MouseFlags.WheeledUp && _host.Orientation == Orientation.Vertical) + || (mouseEvent.Flags == MouseFlags.WheeledLeft && _host.Orientation == Orientation.Horizontal)) + { + _host.Position = Math.Max (_host.Position - 1, 0); + } + else if (mouseEvent.Flags != MouseFlags.ReportMousePosition) + { + return base.OnMouseEvent (mouseEvent); + } + + return true; + } + + /// + protected internal override bool OnMouseLeave (MouseEvent mouseEvent) + { + if (_savedColorScheme is { } && !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) + { + ColorScheme = _savedColorScheme; + _savedColorScheme = null; + } + + return base.OnMouseLeave (mouseEvent); + } + + private int GetPositionFromSliderLocation (int location) + { + if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0) + { + return 0; + } + + int scrollSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.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 + if ((_host.Orientation == Orientation.Vertical && location + Frame.Height == scrollSize) + || (_host.Orientation == Orientation.Horizontal && location + Frame.Width == scrollSize)) + { + return _host.Size - scrollSize; + } + + return Math.Min ((location * _host.Size + location) / scrollSize, _host.Size - scrollSize); + } +} From 4e227060217022f679e16db8ff07826b56fc1128 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 14 Aug 2024 23:41:22 +0100 Subject: [PATCH 026/128] Using overridden methods instead of events. --- Terminal.Gui/Views/Scroll/Scroll.cs | 137 ++++++++++++++-------------- 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 3323354527..22fceb802c 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -20,11 +20,6 @@ public Scroll () _slider = new (this); Add (_slider); - - Added += Scroll_Added!; - Removed += Scroll_Removed!; - Initialized += Scroll_Initialized!; - MouseEvent += Scroll_MouseEvent!; } private readonly ScrollSlider _slider; @@ -35,6 +30,37 @@ public Scroll () private int _size; + /// + public override void EndInit () + { + base.EndInit (); + + AdjustSlider (); + } + + /// + public override void OnAdded (SuperViewChangedEventArgs e) + { + View parent = (e.Parent is Adornment adornment ? adornment.Parent : e.Parent)!; + + parent.LayoutComplete += SuperView_LayoutComplete!; + + base.OnAdded (e); + } + + /// + public override void OnRemoved (SuperViewChangedEventArgs e) + { + if (e.Parent is { }) + { + View parent = (e.Parent is Adornment adornment ? adornment.Parent : e.Parent)!; + + parent.LayoutComplete -= SuperView_LayoutComplete!; + } + + base.OnRemoved (e); + } + /// /// Gets or sets if the Scroll is oriented vertically or horizontally. /// @@ -112,19 +138,49 @@ public int Size /// Raised when has changed. public event EventHandler>? SizeChanged; - // TODO: Move this into "ScrollSlider" and override it there. Scroll can then subscribe to _slider.LayoutComplete and call AdjustSlider. - // QUESTION: I've been meaning to add a "View.FrameChanged" event (fired from LayoutComplete only if Frame has changed). Should we do that as part of this PR? - // QUESTION: Note I *did* add "View.ViewportChanged" in a previous PR. /// - protected override void Dispose (bool disposing) + protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { - Added -= Scroll_Added!; - Initialized -= Scroll_Initialized!; - MouseEvent -= Scroll_MouseEvent!; + int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; + int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; + + (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical + ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) + : new (_slider.Frame.X, _slider.Frame.Right - 1); + + if (mouseEvent.Flags == MouseFlags.Button1Pressed && location < sliderPos.topLeft) + { + Position = Math.Max (Position - barSize, 0); + } + else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location > sliderPos.bottomRight) + { + Position = Math.Min (Position + barSize, Size - barSize); + } + else if ((mouseEvent.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) + || (mouseEvent.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) + { + Position = Math.Min (Position + 1, Size - barSize); + } + else if ((mouseEvent.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical) + || (mouseEvent.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal)) + { + Position = Math.Max (Position - 1, 0); + } + else if (mouseEvent.Flags == MouseFlags.Button1Clicked) + { + if (_slider.Frame.Contains (mouseEvent.Position)) + { + return _slider.OnMouseEvent (mouseEvent); + } + } - base.Dispose (disposing); + return base.OnMouseEvent (mouseEvent); } + // TODO: Move this into "ScrollSlider" and override it there. Scroll can then subscribe to _slider.LayoutComplete and call AdjustSlider. + // QUESTION: I've been meaning to add a "View.FrameChanged" event (fired from LayoutComplete only if Frame has changed). Should we do that as part of this PR? + // QUESTION: Note I *did* add "View.ViewportChanged" in a previous PR. + /// Virtual method called when has changed. Raises . protected virtual void OnPositionChanged (int position) { PositionChanged?.Invoke (this, new (in position)); } @@ -200,64 +256,9 @@ private void AdjustSlider () return new (location, dimension); } - private void Scroll_Added (object sender, SuperViewChangedEventArgs e) - { - View parent = (e.Parent is Adornment adornment ? adornment.Parent : e.Parent)!; - - parent.LayoutComplete += SuperView_LayoutComplete!; - } - - private void Scroll_Initialized (object sender, EventArgs e) { AdjustSlider (); } - // TODO: I think you should create a new `internal` view named "ScrollSlider" with an `Orientation` property. It should inherit from View and override GetNormalColor and the mouse events // that can be moved within it's Superview, constrained to move only horizontally or vertically depending on Orientation. // This will really simplify a lot of this. - private void Scroll_MouseEvent (object sender, MouseEventEventArgs e) - { - MouseEvent me = e.MouseEvent; - int location = Orientation == Orientation.Vertical ? me.Position.Y : me.Position.X; - int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; - - (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical - ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) - : new (_slider.Frame.X, _slider.Frame.Right - 1); - - if (me.Flags == MouseFlags.Button1Pressed && location < sliderPos.topLeft) - { - Position = Math.Max (Position - barSize, 0); - } - else if (me.Flags == MouseFlags.Button1Pressed && location > sliderPos.bottomRight) - { - Position = Math.Min (Position + barSize, Size - barSize); - } - else if ((me.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) - || (me.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) - { - Position = Math.Min (Position + 1, Size - barSize); - } - else if ((me.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical) - || (me.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal)) - { - Position = Math.Max (Position - 1, 0); - } - else if (me.Flags == MouseFlags.Button1Clicked) - { - if (_slider.Frame.Contains (me.Position)) - { - _slider.OnMouseEvent (e.MouseEvent); - } - } - } - - private void Scroll_Removed (object sender, SuperViewChangedEventArgs e) - { - if (e.Parent is { }) - { - View parent = (e.Parent is Adornment adornment ? adornment.Parent : e.Parent)!; - - parent.LayoutComplete -= SuperView_LayoutComplete!; - } - } private void SetSliderText () { From 3317c504e0dff4777b2b5169a85c2e96333a9f04 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 16 Aug 2024 15:17:49 +0100 Subject: [PATCH 027/128] Moving slider code to his class. --- Terminal.Gui/Views/Scroll/Scroll.cs | 119 +++------------------- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 72 +++++++++++++ 2 files changed, 87 insertions(+), 104 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 22fceb802c..900f6e7b45 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -12,14 +12,14 @@ public class Scroll : View /// public Scroll () { + _slider = new (this); + Add (_slider); + WantContinuousButtonPressed = true; CanFocus = false; Orientation = Orientation.Vertical; Width = Dim.Auto (DimAutoStyle.Content, 1); Height = Dim.Auto (DimAutoStyle.Content, 1); - - _slider = new (this); - Add (_slider); } private readonly ScrollSlider _slider; @@ -35,30 +35,7 @@ public override void EndInit () { base.EndInit (); - AdjustSlider (); - } - - /// - public override void OnAdded (SuperViewChangedEventArgs e) - { - View parent = (e.Parent is Adornment adornment ? adornment.Parent : e.Parent)!; - - parent.LayoutComplete += SuperView_LayoutComplete!; - - base.OnAdded (e); - } - - /// - public override void OnRemoved (SuperViewChangedEventArgs e) - { - if (e.Parent is { }) - { - View parent = (e.Parent is Adornment adornment ? adornment.Parent : e.Parent)!; - - parent.LayoutComplete -= SuperView_LayoutComplete!; - } - - base.OnRemoved (e); + AdjustAll (); } /// @@ -70,7 +47,7 @@ public Orientation Orientation set { _orientation = value; - AdjustSlider (); + AdjustAll (); } } @@ -105,7 +82,7 @@ public int Position if (!_slider._wasSliderMouse) { - AdjustSlider (); + AdjustAll (); } OnPositionChanged (_position); @@ -131,7 +108,7 @@ public int Size { _size = value; OnSizeChanged (_size); - AdjustSlider (); + AdjustAll (); } } @@ -199,68 +176,21 @@ protected virtual CancelEventArgs OnPositionChanging (int currentPos, int n /// Virtual method called when has changed. Raises . protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (in size)); } - private void AdjustSlider () + /// + internal override void OnLayoutComplete (LayoutEventArgs args) { - if (!IsInitialized) - { - return; - } - - (int Location, int Dimension) slider = GetSliderLocationDimensionFromPosition (); - _slider.X = Orientation == Orientation.Vertical ? 0 : slider.Location; - _slider.Y = Orientation == Orientation.Vertical ? slider.Location : 0; + base.OnLayoutComplete (args); - _slider.SetContentSize ( - new ( - Orientation == Orientation.Vertical ? GetContentSize ().Width : slider.Dimension, - Orientation == Orientation.Vertical ? slider.Dimension : GetContentSize ().Height - )); - SetSliderText (); + AdjustAll (); } - // QUESTION: This method is only called from one place. Should it be inlined? Or, should it be made internal and unit tests be provided? - private (int Location, int Dimension) GetSliderLocationDimensionFromPosition () + private void AdjustAll () { - if (GetContentSize ().Height == 0 || GetContentSize ().Width == 0) - { - return new (0, 0); - } - - int scrollSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; - int location; - int dimension; - - if (Size > 0) - { - dimension = Math.Min (Math.Max (scrollSize * scrollSize / Size, 1), scrollSize); - - // Ensure the Position is valid - if (Position > 0 && Position + scrollSize > Size) - { - Position = Size - scrollSize; - } - - location = Math.Min ((Position * scrollSize + Position) / Size, scrollSize - dimension); - - if (Position == Size - scrollSize && location + dimension < scrollSize) - { - location = scrollSize - dimension; - } - } - else - { - location = 0; - dimension = scrollSize; - } - - return new (location, dimension); + _slider.AdjustSlider (); + SetScrollText (); } - // TODO: I think you should create a new `internal` view named "ScrollSlider" with an `Orientation` property. It should inherit from View and override GetNormalColor and the mouse events - // that can be moved within it's Superview, constrained to move only horizontally or vertically depending on Orientation. - // This will really simplify a lot of this. - - private void SetSliderText () + private void SetScrollText () { TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; @@ -269,24 +199,5 @@ private void SetSliderText () Enumerable.Repeat ( Glyphs.Stipple.ToString (), GetContentSize ().Width * GetContentSize ().Height)); - _slider.TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; - - _slider.Text = string.Concat ( - Enumerable.Repeat ( - Glyphs.ContinuousMeterSegment.ToString (), - _slider.GetContentSize ().Width * _slider.GetContentSize ().Height)); - } - - // TODO: This is unnecessary. If Scroll.Width/Height is Dim.Auto, the Superview will get resized automatically. - private void SuperView_LayoutComplete (object sender, LayoutEventArgs e) - { - if (!_slider._wasSliderMouse) - { - AdjustSlider (); - } - else - { - _slider._wasSliderMouse = false; - } } } diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 106b76f905..311bfce926 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -19,6 +19,25 @@ public ScrollSlider (Scroll host) private int _lastLocation = -1; private ColorScheme? _savedColorScheme; + public void AdjustSlider () + { + if (!IsInitialized) + { + return; + } + + (int Location, int Dimension) sliderLocationAndDimension = GetSliderLocationDimensionFromPosition (); + X = _host.Orientation == Orientation.Vertical ? 0 : sliderLocationAndDimension.Location; + Y = _host.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Location : 0; + + SetContentSize ( + new ( + _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Width : sliderLocationAndDimension.Dimension, + _host.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Dimension : _host.GetContentSize ().Height + )); + SetSliderText (); + } + /// public override Attribute GetNormalColor () { @@ -145,4 +164,57 @@ private int GetPositionFromSliderLocation (int location) return Math.Min ((location * _host.Size + location) / scrollSize, _host.Size - scrollSize); } + + // QUESTION: This method is only called from one place. Should it be inlined? Or, should it be made internal and unit tests be provided? + private (int Location, int Dimension) GetSliderLocationDimensionFromPosition () + { + if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0) + { + return new (0, 0); + } + + int scrollSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width; + int location; + int dimension; + + if (_host.Size > 0) + { + dimension = Math.Min (Math.Max (scrollSize * scrollSize / _host.Size, 1), scrollSize); + + // Ensure the Position is valid + if (_host.Position > 0 && _host.Position + scrollSize > _host.Size) + { + _host.Position = _host.Size - scrollSize; + } + + location = Math.Min ((_host.Position * scrollSize + _host.Position) / _host.Size, scrollSize - dimension); + + if (_host.Position == _host.Size - scrollSize && location + dimension < scrollSize) + { + location = scrollSize - dimension; + } + } + else + { + location = 0; + dimension = scrollSize; + } + + return new (location, dimension); + } + + // TODO: I think you should create a new `internal` view named "ScrollSlider" with an `Orientation` property. It should inherit from View and override GetNormalColor and the mouse events + // that can be moved within it's Superview, constrained to move only horizontally or vertically depending on Orientation. + // This will really simplify a lot of this. + + private void SetSliderText () + { + TextDirection = _host.Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; + + // QUESTION: Should these Glyphs be configurable via CM? + Text = string.Concat ( + Enumerable.Repeat ( + Glyphs.ContinuousMeterSegment.ToString (), + _host.GetContentSize ().Width * _host.GetContentSize ().Height)); + } } From b025beb32fc9872fe910a6d436d0b558e54f3efc Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 16 Aug 2024 16:27:54 +0100 Subject: [PATCH 028/128] Rename id to scrollSlider to avoid conflict with the Slider view. --- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 2 +- UnitTests/Views/ScrollTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 311bfce926..72624a06b8 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -7,7 +7,7 @@ internal class ScrollSlider : View public ScrollSlider (Scroll host) { _host = host; - Id = "slider"; + Id = "scrollSlider"; Width = Dim.Auto (DimAutoStyle.Content); Height = Dim.Auto (DimAutoStyle.Content); WantMousePositionReports = true; diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index f39091c264..c32b248443 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -692,7 +692,7 @@ string expectedOut }); } - Assert.Equal ("slider", Application.MouseGrabView?.Id); + Assert.Equal ("scrollSlider", Application.MouseGrabView?.Id); Assert.Equal (expectedPos, scroll.Position); Application.Refresh (); From 9b896573ece09018ffe7502157209395b046ed7d Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 16 Aug 2024 23:57:15 +0100 Subject: [PATCH 029/128] Fix scroll slider when moving mouse outside the host. --- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 22 ++- UnitTests/Views/ScrollTests.cs | 180 +++++++++++++--------- 2 files changed, 119 insertions(+), 83 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 72624a06b8..9b7860217d 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -87,23 +87,21 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) } else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + _wasSliderMouse = true; + if (_host.Orientation == Orientation.Vertical) { - if (Frame.Y + offset >= 0 && Frame.Y + offset + Frame.Height <= barSize) - { - _wasSliderMouse = true; - Y = Frame.Y + offset; - _host.Position = GetPositionFromSliderLocation (Frame.Y); - } + Y = Frame.Y + offset < 0 ? 0 : + Frame.Y + offset + Frame.Height > barSize ? Math.Max (barSize - Frame.Height, 0) : Frame.Y + offset; + + _host.Position = GetPositionFromSliderLocation (Frame.Y); } else { - if (Frame.X + offset >= 0 && Frame.X + offset + Frame.Width <= barSize) - { - _wasSliderMouse = true; - X = Frame.X + offset; - _host.Position = GetPositionFromSliderLocation (Frame.X); - } + X = Frame.X + offset < 0 ? 0 : + Frame.X + offset + Frame.Width > barSize ? Math.Max (barSize - Frame.Width, 0) : Frame.X + offset; + + _host.Position = GetPositionFromSliderLocation (Frame.X); } } else if (mouseEvent.Flags == MouseFlags.Button1Released) diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index c32b248443..6376cf822b 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -707,6 +707,45 @@ string expectedOut Assert.Null (Application.MouseGrabView); } + [Theory] + [AutoInitShutdown] + [InlineData (Orientation.Vertical)] + public void Moving_Mouse_Outside_Host_Ensures_Correct_Location (Orientation orientation) + { + var scroll = new Scroll + { + X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, + Position = 5, Orientation = orientation + }; + var top = new Toplevel (); + top.Add (scroll); + Application.Begin (top); + + Rectangle scrollSliderFrame = scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame; + Assert.Equal (scrollSliderFrame, orientation == Orientation.Vertical ? new (0, 2, 1, 5) : new (2, 0, 5, 1)); + + Application.OnMouseEvent (new () { Position = orientation == Orientation.Vertical ? new (10, 12) : new (12, 10), Flags = MouseFlags.Button1Pressed }); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (10, 0) : new (0, 10), + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + }); + Assert.Equal (new (0, 0), scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame.Location); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, 25) : new (80, 0), + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + }); + + Assert.Equal ( + orientation == Orientation.Vertical ? new (0, 5) : new (5, 0), + scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame.Location); + } + [Theory] [InlineData (Orientation.Vertical, 20, 10)] [InlineData (Orientation.Vertical, 40, 30)] @@ -754,6 +793,76 @@ public void PositionChanging_Cancelable_And_PositionChanged_Events () Assert.Equal (1, changedCount); } + [Fact] + public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position_Was_Really_Changed () + { + var changing = 0; + var cancel = false; + var changed = 0; + var scroll = new Scroll { Height = 10, Size = 20 }; + scroll.PositionChanging += Scroll_PositionChanging; + scroll.PositionChanged += Scroll_PositionChanged; + + Assert.Equal (Orientation.Vertical, scroll.Orientation); + Assert.Equal (new (0, 0, 1, 10), scroll.Viewport); + Assert.Equal (0, scroll.Position); + Assert.Equal (0, changing); + Assert.Equal (0, changed); + + scroll.Position = 0; + Assert.Equal (0, scroll.Position); + Assert.Equal (0, changing); + Assert.Equal (0, changed); + + scroll.Position = 1; + Assert.Equal (1, scroll.Position); + Assert.Equal (1, changing); + Assert.Equal (1, changed); + + Reset (); + cancel = true; + scroll.Position = 2; + Assert.Equal (1, scroll.Position); + Assert.Equal (1, changing); + Assert.Equal (0, changed); + + Reset (); + scroll.Position = 10; + Assert.Equal (10, scroll.Position); + Assert.Equal (1, changing); + Assert.Equal (1, changed); + + Reset (); + scroll.Position = 11; + Assert.Equal (10, scroll.Position); + Assert.Equal (0, changing); + Assert.Equal (0, changed); + + Reset (); + scroll.Position = 0; + Assert.Equal (0, scroll.Position); + Assert.Equal (1, changing); + Assert.Equal (1, changed); + + scroll.PositionChanging -= Scroll_PositionChanging; + scroll.PositionChanged -= Scroll_PositionChanged; + + void Scroll_PositionChanging (object sender, CancelEventArgs e) + { + changing++; + e.Cancel = cancel; + } + + void Scroll_PositionChanged (object sender, EventArgs e) { changed++; } + + void Reset () + { + changing = 0; + cancel = false; + changed = 0; + } + } + [Fact] public void SizeChanged_Event () { @@ -843,75 +952,4 @@ public void Vertical_Horizontal_Draws_Correctly (int sizeWidth, int sizeHeight, _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); } - - [Fact] - public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position_Was_Really_Changed () - { - var changing = 0; - var cancel = false; - var changed = 0; - var scroll = new Scroll { Height = 10, Size = 20 }; - scroll.PositionChanging += Scroll_PositionChanging; - scroll.PositionChanged += Scroll_PositionChanged; - - Assert.Equal (Orientation.Vertical, scroll.Orientation); - Assert.Equal (new (0, 0, 1, 10), scroll.Viewport); - Assert.Equal (0, scroll.Position); - Assert.Equal (0, changing); - Assert.Equal (0, changed); - - scroll.Position = 0; - Assert.Equal (0, scroll.Position); - Assert.Equal (0, changing); - Assert.Equal (0, changed); - - scroll.Position = 1; - Assert.Equal (1, scroll.Position); - Assert.Equal (1, changing); - Assert.Equal (1, changed); - - Reset (); - cancel = true; - scroll.Position = 2; - Assert.Equal (1, scroll.Position); - Assert.Equal (1, changing); - Assert.Equal (0, changed); - - Reset (); - scroll.Position = 10; - Assert.Equal (10, scroll.Position); - Assert.Equal (1, changing); - Assert.Equal (1, changed); - - Reset (); - scroll.Position = 11; - Assert.Equal (10, scroll.Position); - Assert.Equal (0, changing); - Assert.Equal (0, changed); - - Reset (); - scroll.Position = 0; - Assert.Equal (0, scroll.Position); - Assert.Equal (1, changing); - Assert.Equal (1, changed); - - scroll.PositionChanging -= Scroll_PositionChanging; - scroll.PositionChanged -= Scroll_PositionChanged; - - - void Scroll_PositionChanging (object sender, CancelEventArgs e) - { - changing++; - e.Cancel = cancel; - } - - void Scroll_PositionChanged (object sender, EventArgs e) => changed++; - - void Reset () - { - changing = 0; - cancel = false; - changed = 0; - } - } } From 3386d06c315a32d3e8eda675541a709aedb35683 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 17 Aug 2024 00:03:54 +0100 Subject: [PATCH 030/128] Ensure correct Width/Height when orientation is changed. --- UICatalog/Scenarios/ScrollDemo.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/UICatalog/Scenarios/ScrollDemo.cs b/UICatalog/Scenarios/ScrollDemo.cs index c86a1b2996..9fdb0226dc 100644 --- a/UICatalog/Scenarios/ScrollDemo.cs +++ b/UICatalog/Scenarios/ScrollDemo.cs @@ -1,4 +1,5 @@ -using Terminal.Gui; +using System; +using Terminal.Gui; namespace UICatalog.Scenarios; @@ -56,8 +57,13 @@ public override void Main () scrollWidthHeight.ValueChanging += (s, e) => { - if (e.NewValue < 1) + if (e.NewValue < 1 + || (e.NewValue + > (scroll.Orientation == Orientation.Vertical + ? scroll.SuperView?.GetContentSize ().Width + : scroll.SuperView?.GetContentSize ().Height))) { + // TODO: This must be handled in the ScrollSlider if Width and Height being virtual e.Cancel = true; return; @@ -93,6 +99,7 @@ public override void Main () scroll.Orientation = Orientation.Vertical; scroll.X = Pos.AnchorEnd (); scroll.Y = 0; + scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scroll.SuperView.GetContentSize ().Width); scroll.Width = scrollWidthHeight.Value; scroll.Height = Dim.Fill (); scroll.Size /= 3; @@ -103,6 +110,8 @@ public override void Main () scroll.X = 0; scroll.Y = Pos.AnchorEnd (); scroll.Width = Dim.Fill (); + + scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scroll.SuperView.GetContentSize ().Height); scroll.Height = scrollWidthHeight.Value; scroll.Size *= 3; } From e59c02ad272cdffa79993b68a264e08d94807ce2 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 19 Aug 2024 13:16:48 +0100 Subject: [PATCH 031/128] Rename to AdjustScroll method. --- Terminal.Gui/Views/Scroll/Scroll.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 900f6e7b45..9053fb4e16 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -35,7 +35,7 @@ public override void EndInit () { base.EndInit (); - AdjustAll (); + AdjustScroll (); } /// @@ -47,7 +47,7 @@ public Orientation Orientation set { _orientation = value; - AdjustAll (); + AdjustScroll (); } } @@ -82,7 +82,7 @@ public int Position if (!_slider._wasSliderMouse) { - AdjustAll (); + AdjustScroll (); } OnPositionChanged (_position); @@ -108,7 +108,7 @@ public int Size { _size = value; OnSizeChanged (_size); - AdjustAll (); + AdjustScroll (); } } @@ -181,10 +181,10 @@ internal override void OnLayoutComplete (LayoutEventArgs args) { base.OnLayoutComplete (args); - AdjustAll (); + AdjustScroll (); } - private void AdjustAll () + private void AdjustScroll () { _slider.AdjustSlider (); SetScrollText (); From 7e3a3b2eafcdba285e55ec03c10c20c883b8d136 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 21 Aug 2024 19:42:46 +0100 Subject: [PATCH 032/128] Fixes #3679. WantContinuousButtonPressed mustn't force calling GrabMouse. --- Terminal.Gui/View/View.Mouse.cs | 2 +- UnitTests/View/MouseTests.cs | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/View/View.Mouse.cs b/Terminal.Gui/View/View.Mouse.cs index 5f1318e214..44553f6ae9 100644 --- a/Terminal.Gui/View/View.Mouse.cs +++ b/Terminal.Gui/View/View.Mouse.cs @@ -87,7 +87,7 @@ public partial class View // Mouse APIs return mouseEvent.Handled = true; } - if (HighlightStyle != HighlightStyle.None || WantContinuousButtonPressed) + if (HighlightStyle != HighlightStyle.None || (WantContinuousButtonPressed && WantMousePositionReports)) { if (HandlePressed (mouseEvent)) { diff --git a/UnitTests/View/MouseTests.cs b/UnitTests/View/MouseTests.cs index 56548aff4c..6e3e876d95 100644 --- a/UnitTests/View/MouseTests.cs +++ b/UnitTests/View/MouseTests.cs @@ -232,7 +232,7 @@ public void WantContinuousButtonPressed_True_Button_Clicked_Clicks (MouseFlags c [InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released)] [InlineData (MouseFlags.Button3Pressed, MouseFlags.Button3Released)] [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released)] - public void WantContinuousButtonPressed_True_Button_Press_Release_Clicks (MouseFlags pressed, MouseFlags released) + public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Button_Press_Release_Clicks (MouseFlags pressed, MouseFlags released) { var me = new MouseEvent (); @@ -240,7 +240,8 @@ public void WantContinuousButtonPressed_True_Button_Press_Release_Clicks (MouseF { Width = 1, Height = 1, - WantContinuousButtonPressed = true + WantContinuousButtonPressed = true, + WantMousePositionReports = true }; var clickedCount = 0; @@ -263,7 +264,7 @@ public void WantContinuousButtonPressed_True_Button_Press_Release_Clicks (MouseF [InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released, MouseFlags.Button2Clicked)] [InlineData (MouseFlags.Button3Pressed, MouseFlags.Button3Released, MouseFlags.Button3Clicked)] [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)] - public void WantContinuousButtonPressed_True_Button_Press_Release_Clicks_Repeatedly (MouseFlags pressed, MouseFlags released, MouseFlags clicked) + public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Button_Press_Release_Clicks_Repeatedly (MouseFlags pressed, MouseFlags released, MouseFlags clicked) { var me = new MouseEvent (); @@ -271,7 +272,8 @@ public void WantContinuousButtonPressed_True_Button_Press_Release_Clicks_Repeate { Width = 1, Height = 1, - WantContinuousButtonPressed = true + WantContinuousButtonPressed = true, + WantMousePositionReports = true }; var clickedCount = 0; @@ -301,7 +303,7 @@ public void WantContinuousButtonPressed_True_Button_Press_Release_Clicks_Repeate } [Fact] - public void WantContinuousButtonPressed_True_Move_InViewport_OutOfViewport_Keeps_Counting () + public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Move_InViewport_OutOfViewport_Keeps_Counting () { var me = new MouseEvent (); @@ -309,7 +311,8 @@ public void WantContinuousButtonPressed_True_Move_InViewport_OutOfViewport_Keeps { Width = 1, Height = 1, - WantContinuousButtonPressed = true + WantContinuousButtonPressed = true, + WantMousePositionReports = true }; var clickedCount = 0; From 81888222eabd697ba7f0c50b7f747d2aeccf64d7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 21 Aug 2024 22:07:25 +0100 Subject: [PATCH 033/128] Ensures slider to have a length proportional to the bar size. --- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 2 +- UnitTests/Views/ScrollTests.cs | 80 +++++++++++------------ 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 9b7860217d..aa4a641d43 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -177,7 +177,7 @@ private int GetPositionFromSliderLocation (int location) if (_host.Size > 0) { - dimension = Math.Min (Math.Max (scrollSize * scrollSize / _host.Size, 1), scrollSize); + dimension = (int)Math.Min (Math.Max (Math.Ceiling ((double)scrollSize * scrollSize / _host.Size), 1), scrollSize); // Ensure the Position is valid if (_host.Position > 0 && _host.Position + scrollSize > _host.Size) diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index 6376cf822b..de0e39e26e 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -49,7 +49,7 @@ public class ScrollTests ░ █ █ -░ +█ ░ ░ ░ @@ -62,13 +62,13 @@ public class ScrollTests @" ░░░░░█████", @" -░░██░░░░░░")] +░░███░░░░░")] [InlineData ( 40, @" █ █ -░ +█ ░ ░ ░ @@ -80,7 +80,7 @@ public class ScrollTests ░ █ █ -░ +█ ░ ░ ░ @@ -92,7 +92,7 @@ public class ScrollTests ░ █ █ -░ +█ ░ ░ ░ @@ -101,7 +101,7 @@ public class ScrollTests @" ░ █ -░ +█ ░ ░ ░ @@ -110,13 +110,13 @@ public class ScrollTests ░ ░", @" -██░░░░░░░░", +███░░░░░░░", @" -░██░░░░░░░", +░███░░░░░░", @" -░░██░░░░░░", +░░███░░░░░", @" -░█░░░░░░░░")] +░██░░░░░░░")] public void Changing_Position_Size_Orientation_Draws_Correctly ( int size, string firstVertExpected, @@ -232,7 +232,7 @@ public void Constructor_Defaults () ░ █ █ -░ +█ ░ ░ ░ @@ -247,7 +247,7 @@ public void Constructor_Defaults () ░ █ █ -░ +█ ░ ░")] [InlineData ( @@ -266,10 +266,10 @@ public void Constructor_Defaults () 10, 5, @" -░░██░░░░░░", +░░███░░░░░", 20, @" -░░░░░██░░░")] +░░░░░███░░")] public void Mouse_On_The_Container (Orientation orientation, int size, int position, int location, string output, int expectedPos, string expectedOut) { var scroll = new Scroll @@ -340,7 +340,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit ░ █ █ -░ +█ ░ ░ ░ @@ -353,7 +353,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit ░ █ █ -░ +█ ░ ░ ░ @@ -378,11 +378,11 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit 3, 3, @" -░░██░░░░░░", +░░███░░░░░", MouseFlags.Button1Pressed, 10, @" -░░██░░░░░░")] +░░███░░░░░")] [InlineData ( Orientation.Vertical, 20, @@ -478,7 +478,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit ░ █ █ -░ +█ ░ ░ ░ @@ -490,7 +490,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit ░ █ █ -░ +█ ░ ░ ░ @@ -504,11 +504,11 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit 2, 1, @" -░░██░░░░░░", +░░███░░░░░", MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, 4, @" -░██░░░░░░░")] +░███░░░░░░")] [InlineData ( Orientation.Vertical, 40, @@ -520,7 +520,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit ░ █ █ -░ +█ ░ ░ ░ @@ -534,7 +534,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit ░ █ █ -░ +█ ░ ░ ░ @@ -546,11 +546,11 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit 3, 4, @" -░░██░░░░░░", +░░███░░░░░", MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, 12, @" -░░░██░░░░░")] +░░░███░░░░")] [InlineData ( Orientation.Vertical, 40, @@ -562,7 +562,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit ░ █ █ -░ +█ ░ ░ ░ @@ -576,7 +576,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit ░ █ █ -░ +█ ░ ░ ░ @@ -588,11 +588,11 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit 2, 3, @" -░░██░░░░░░", +░░███░░░░░", MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, 12, @" -░░░██░░░░░")] +░░░███░░░░")] [InlineData ( Orientation.Vertical, 40, @@ -604,7 +604,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit ░ █ █ -░ +█ ░ ░ ░ @@ -619,7 +619,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit ░ █ █ -░ +█ ░ ░ ░")] @@ -630,11 +630,11 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit 2, 4, @" -░░██░░░░░░", +░░███░░░░░", MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, 16, @" -░░░░██░░░░")] +░░░░███░░░")] public void Mouse_On_The_Slider ( Orientation orientation, int size, @@ -887,7 +887,7 @@ public void SizeChanged_Event () │█│ │█│ │█│ -│░│ +│█│ │░│ │░│ │░│ @@ -900,7 +900,7 @@ public void SizeChanged_Event () Orientation.Horizontal, @" ┌────────┐ -│███░░░░░│ +│████░░░░│ └────────┘")] [InlineData ( 3, @@ -912,7 +912,7 @@ public void SizeChanged_Event () │███│ │███│ │███│ -│░░░│ +│███│ │░░░│ │░░░│ │░░░│ @@ -925,9 +925,9 @@ public void SizeChanged_Event () Orientation.Horizontal, @" ┌────────┐ -│███░░░░░│ -│███░░░░░│ -│███░░░░░│ +│████░░░░│ +│████░░░░│ +│████░░░░│ └────────┘")] public void Vertical_Horizontal_Draws_Correctly (int sizeWidth, int sizeHeight, int widthHeight, Orientation orientation, string expected) { From 92e067e0146559a592cc9ad43f013a9325e473e6 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 21 Aug 2024 22:15:17 +0100 Subject: [PATCH 034/128] Prevents continuous mouse button pressed from processing mouse event without slider completed the layout. --- Terminal.Gui/Views/Scroll/Scroll.cs | 27 ++++++++++++++++++----- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 16 +++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 9053fb4e16..19de2de1c1 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -22,12 +22,12 @@ public Scroll () Height = Dim.Auto (DimAutoStyle.Content, 1); } - private readonly ScrollSlider _slider; - private Orientation _orientation; + internal bool _wasSliderLayoutComplete = true; + private readonly ScrollSlider _slider; + private Orientation _orientation; private int _position; - private int _size; /// @@ -118,6 +118,12 @@ public int Size /// protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { + if (!_wasSliderLayoutComplete) + { + // Do not process if slider layout wasn't yet completed + return base.OnMouseEvent (mouseEvent); + } + int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; @@ -125,11 +131,11 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) : new (_slider.Frame.X, _slider.Frame.Right - 1); - if (mouseEvent.Flags == MouseFlags.Button1Pressed && location < sliderPos.topLeft) + if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location < sliderPos.topLeft) { Position = Math.Max (Position - barSize, 0); } - else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location > sliderPos.bottomRight) + else if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location > sliderPos.bottomRight) { Position = Math.Min (Position + barSize, Size - barSize); } @@ -150,10 +156,21 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) return _slider.OnMouseEvent (mouseEvent); } } + // Flag as false until slider layout is completed + _wasSliderLayoutComplete = false; return base.OnMouseEvent (mouseEvent); } + /// + protected internal override bool OnMouseLeave (MouseEvent mouseEvent) + { + // If scroll isn't handling mouse then reset the flag + _wasSliderLayoutComplete = true; + + return base.OnMouseLeave (mouseEvent); + } + // TODO: Move this into "ScrollSlider" and override it there. Scroll can then subscribe to _slider.LayoutComplete and call AdjustSlider. // QUESTION: I've been meaning to add a "View.FrameChanged" event (fired from LayoutComplete only if Frame has changed). Should we do that as part of this PR? // QUESTION: Note I *did* add "View.ViewportChanged" in a previous PR. diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index aa4a641d43..075fa3c9e7 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -73,11 +73,17 @@ public override Attribute GetNormalColor () /// protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { + if (!_host._wasSliderLayoutComplete) + { + // Ensure not blocking scroll mouse event + _host._wasSliderLayoutComplete = true; + } + int location = _host.Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; int offset = _lastLocation > -1 ? location - _lastLocation : 0; int barSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width; - if (mouseEvent.Flags == MouseFlags.Button1Pressed) + if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && _lastLocation == -1) { if (Application.MouseGrabView != this) { @@ -143,6 +149,14 @@ protected internal override bool OnMouseLeave (MouseEvent mouseEvent) return base.OnMouseLeave (mouseEvent); } + /// + internal override void OnLayoutComplete (LayoutEventArgs args) + { + base.OnLayoutComplete (args); + + _host._wasSliderLayoutComplete = true; + } + private int GetPositionFromSliderLocation (int location) { if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0) From 5e7bb7bd6c43cf245b7df8364bfbe65f7d9f8db7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 23 Aug 2024 16:45:41 +0100 Subject: [PATCH 035/128] Starting implementing ScrollBar. --- Terminal.Gui/Views/Scroll/Scroll.cs | 26 ++- Terminal.Gui/Views/Scroll/ScrollBar.cs | 118 ++++++++++ Terminal.Gui/Views/Scroll/ScrollButton.cs | 143 ++++++++++++ UICatalog/Scenarios/ScrollBarDemo.cs | 259 ++++++++++++++++++++++ 4 files changed, 539 insertions(+), 7 deletions(-) create mode 100644 Terminal.Gui/Views/Scroll/ScrollBar.cs create mode 100644 Terminal.Gui/Views/Scroll/ScrollButton.cs create mode 100644 UICatalog/Scenarios/ScrollBarDemo.cs diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 19de2de1c1..230008797e 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -10,8 +10,11 @@ namespace Terminal.Gui; public class Scroll : View { /// - public Scroll () + public Scroll () : this (null) { } + + public Scroll (ScrollBar? host) { + _host = host; _slider = new (this); Add (_slider); @@ -23,6 +26,7 @@ public Scroll () } + internal readonly ScrollBar? _host; internal bool _wasSliderLayoutComplete = true; private readonly ScrollSlider _slider; @@ -193,6 +197,20 @@ protected virtual CancelEventArgs OnPositionChanging (int currentPos, int n /// Virtual method called when has changed. Raises . protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (in size)); } + internal void AdjustScroll () + { + if (_host is { }) + { + X = Orientation == Orientation.Vertical ? 0 : 1; + Y = Orientation == Orientation.Vertical ? 1 : 0; + Width = Orientation == Orientation.Vertical ? Dim.Fill () : Dim.Fill (1); + Height = Orientation == Orientation.Vertical ? Dim.Fill (1) : Dim.Fill (); + } + + _slider.AdjustSlider (); + SetScrollText (); + } + /// internal override void OnLayoutComplete (LayoutEventArgs args) { @@ -201,12 +219,6 @@ internal override void OnLayoutComplete (LayoutEventArgs args) AdjustScroll (); } - private void AdjustScroll () - { - _slider.AdjustSlider (); - SetScrollText (); - } - private void SetScrollText () { TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs new file mode 100644 index 0000000000..39f3cf2025 --- /dev/null +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -0,0 +1,118 @@ +#nullable enable + +using System.ComponentModel; + +namespace Terminal.Gui; + +/// ScrollBars are views that display a 1-character scrollbar, either horizontal or vertical +/// +/// +/// The scrollbar is drawn to be a representation of the Size, assuming that the scroll position is set at +/// Position. +/// +/// If the region to display the scrollbar is larger than three characters, arrow indicators are drawn. +/// +public class ScrollBar : View +{ + /// + public ScrollBar () + { + _scroll = new (this); + _decrease = new (this); + _increase = new (this, VariationMode.Increase); + Add (_scroll, _decrease, _increase); + + CanFocus = false; + Orientation = Orientation.Vertical; + Width = Dim.Auto (DimAutoStyle.Content, 1); + Height = Dim.Auto (DimAutoStyle.Content, 1); + + _scroll.PositionChanging += Scroll_PositionChanging; + _scroll.PositionChanged += Scroll_PositionChanged; + _scroll.SizeChanged += _scroll_SizeChanged; + } + + private readonly Scroll _scroll; + private readonly ScrollButton _decrease; + private readonly ScrollButton _increase; + + /// Defines if a scrollbar is vertical or horizontal. + public Orientation Orientation + { + get => _scroll.Orientation; + set + { + Resize (value); + _scroll.Orientation = value; + } + } + + /// The position, relative to , to set the scrollbar at. + /// The position. + public int Position + { + get => _scroll.Position; + set + { + _scroll.Position = value; + AdjustAll (); + } + } + + /// Raised when the has changed. + public event EventHandler>? PositionChanged; + + /// + /// Raised when the is changing. Set to + /// to prevent the position from being changed. + /// + public event EventHandler>? PositionChanging; + + public int Size + { + get => _scroll.Size; + set + { + _scroll.Size = value; + AdjustAll (); + } + } + + /// Raised when has changed. + public event EventHandler>? SizeChanged; + + /// + internal override void OnLayoutComplete (LayoutEventArgs args) + { + base.OnLayoutComplete (args); + + AdjustAll (); + } + + private void _scroll_SizeChanged (object? sender, EventArgs e) { SizeChanged?.Invoke (this, e); } + + private void AdjustAll () + { + _scroll.AdjustScroll (); + _decrease.AdjustButton (); + _increase.AdjustButton (); + } + + private void Resize (Orientation orientation) + { + switch (orientation) + { + case Orientation.Horizontal: + + break; + case Orientation.Vertical: + break; + default: + throw new ArgumentOutOfRangeException (nameof (orientation), orientation, null); + } + } + + private void Scroll_PositionChanged (object? sender, EventArgs e) { PositionChanged?.Invoke (this, e); } + + private void Scroll_PositionChanging (object? sender, CancelEventArgs e) { PositionChanging?.Invoke (this, e); } +} diff --git a/Terminal.Gui/Views/Scroll/ScrollButton.cs b/Terminal.Gui/Views/Scroll/ScrollButton.cs new file mode 100644 index 0000000000..589f6433e9 --- /dev/null +++ b/Terminal.Gui/Views/Scroll/ScrollButton.cs @@ -0,0 +1,143 @@ +#nullable enable + +namespace Terminal.Gui; + +internal enum VariationMode +{ + Decrease, + Increase +} + +internal class ScrollButton : View +{ + public ScrollButton (ScrollBar host, VariationMode variation = VariationMode.Decrease) + { + _host = host; + VariationMode = variation; + TextAlignment = Alignment.Center; + VerticalTextAlignment = Alignment.Center; + Id = "scrollButton"; + + //Width = Dim.Auto (DimAutoStyle.Content, 1); + //Height = Dim.Auto (DimAutoStyle.Content, 1); + WantContinuousButtonPressed = true; + } + + private readonly ScrollBar _host; + private ColorScheme? _savedColorScheme; + + public void AdjustButton () + { + if (!IsInitialized) + { + return; + } + + Width = _host.Orientation == Orientation.Vertical ? Dim.Fill () : 1; + Height = _host.Orientation == Orientation.Vertical ? 1 : Dim.Fill (); + + switch (VariationMode) + { + case VariationMode.Decrease: + X = 0; + Y = 0; + + break; + case VariationMode.Increase: + X = _host.Orientation == Orientation.Vertical ? 0 : Pos.AnchorEnd (1); + Y = _host.Orientation == Orientation.Vertical ? Pos.AnchorEnd (1) : 0; + + break; + default: + throw new ArgumentOutOfRangeException (); + } + + SetButtonText (); + } + + /// + public override Attribute GetNormalColor () + { + if (_savedColorScheme is null) + { + ColorScheme = new () { Normal = new (_host.ColorScheme.HotNormal.Foreground, _host.ColorScheme.HotNormal.Background) }; + } + else + { + ColorScheme = new () { Normal = new (_host.ColorScheme.Normal.Background, _host.ColorScheme.Normal.Foreground) }; + } + + return base.GetNormalColor (); + } + + public VariationMode VariationMode { get; } + + /// + protected internal override bool? OnMouseEnter (MouseEvent mouseEvent) + { + _savedColorScheme ??= _host.ColorScheme; + + ColorScheme = new () + { + Normal = new (_savedColorScheme.HotNormal.Foreground, _savedColorScheme.HotNormal.Foreground), + Focus = new (_savedColorScheme.Focus.Foreground, _savedColorScheme.Focus.Foreground), + HotNormal = new (_savedColorScheme.Normal.Foreground, _savedColorScheme.Normal.Foreground), + HotFocus = new (_savedColorScheme.HotFocus.Foreground, _savedColorScheme.HotFocus.Foreground), + Disabled = new (_savedColorScheme.Disabled.Foreground, _savedColorScheme.Disabled.Foreground) + }; + + return base.OnMouseEnter (mouseEvent); + } + + /// + protected internal override bool OnMouseEvent (MouseEvent mouseEvent) + { + if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) + { + switch (VariationMode) + { + case VariationMode.Decrease: + _host.Position--; + + return true; + case VariationMode.Increase: + _host.Position++; + + return true; + default: + throw new ArgumentOutOfRangeException (); + } + } + + return base.OnMouseEvent (mouseEvent); + } + + /// + protected internal override bool OnMouseLeave (MouseEvent mouseEvent) + { + if (_savedColorScheme is { } && !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) + { + ColorScheme = _savedColorScheme; + _savedColorScheme = null; + } + + return base.OnMouseLeave (mouseEvent); + } + + private void SetButtonText () + { + switch (VariationMode) + { + case VariationMode.Decrease: + Text = _host.Orientation == Orientation.Vertical ? Glyphs.UpArrow.ToString () : Glyphs.LeftArrow.ToString (); + + break; + case VariationMode.Increase: + Text = _host.Orientation == Orientation.Vertical ? Glyphs.DownArrow.ToString () : Glyphs.RightArrow.ToString (); + + break; + default: + throw new ArgumentOutOfRangeException (); + } + } +} diff --git a/UICatalog/Scenarios/ScrollBarDemo.cs b/UICatalog/Scenarios/ScrollBarDemo.cs new file mode 100644 index 0000000000..284c7f40d6 --- /dev/null +++ b/UICatalog/Scenarios/ScrollBarDemo.cs @@ -0,0 +1,259 @@ +using System; +using Terminal.Gui; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("ScrollBar Demo", "Demonstrates using ScrollBar view.")] +[ScenarioCategory ("Drawing")] +[ScenarioCategory ("Scrolling")] +public class ScrollBarDemo : Scenario +{ + private ViewDiagnosticFlags _diagnosticFlags; + + public override void Main () + { + Application.Init (); + + _diagnosticFlags = View.Diagnostics; + + Window app = new () + { + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" + }; + + var editor = new AdornmentsEditor (); + app.Add (editor); + + var view = new FrameView + { + Title = "Demo View", + X = Pos.Right (editor), + Width = Dim.Fill (), + Height = Dim.Fill (), + ColorScheme = Colors.ColorSchemes ["Base"] + }; + app.Add (view); + + var scrollBar = new ScrollBar + { + X = Pos.AnchorEnd (), + Height = Dim.Fill (), + }; + view.Add (scrollBar); + + var lblWidthHeight = new Label + { + Text = "Width/Height:" + }; + view.Add (lblWidthHeight); + + NumericUpDown scrollWidthHeight = new () + { + Value = scrollBar.Frame.Width, + X = Pos.Right (lblWidthHeight) + 1, + Y = Pos.Top (lblWidthHeight) + }; + view.Add (scrollWidthHeight); + + scrollWidthHeight.ValueChanging += (s, e) => + { + if (e.NewValue < 1 + || (e.NewValue + > (scrollBar.Orientation == Orientation.Vertical + ? scrollBar.SuperView?.GetContentSize ().Width + : scrollBar.SuperView?.GetContentSize ().Height))) + { + // TODO: This must be handled in the ScrollSlider if Width and Height being virtual + e.Cancel = true; + + return; + } + + if (scrollBar.Orientation == Orientation.Vertical) + { + scrollBar.Width = e.NewValue; + } + else + { + scrollBar.Height = e.NewValue; + } + }; + + var rgOrientation = new RadioGroup + { + Y = Pos.Bottom (lblWidthHeight), + RadioLabels = ["Vertical", "Horizontal"], + Orientation = Orientation.Horizontal + }; + view.Add (rgOrientation); + + rgOrientation.SelectedItemChanged += (s, e) => + { + if (e.SelectedItem == e.PreviousSelectedItem) + { + return; + } + + if (rgOrientation.SelectedItem == 0) + { + scrollBar.Orientation = Orientation.Vertical; + scrollBar.X = Pos.AnchorEnd (); + scrollBar.Y = 0; + scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scrollBar.SuperView.GetContentSize ().Width); + scrollBar.Width = scrollWidthHeight.Value; + scrollBar.Height = Dim.Fill (); + scrollBar.Size /= 3; + } + else + { + scrollBar.Orientation = Orientation.Horizontal; + scrollBar.X = 0; + scrollBar.Y = Pos.AnchorEnd (); + scrollBar.Width = Dim.Fill (); + + scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scrollBar.SuperView.GetContentSize ().Height); + scrollBar.Height = scrollWidthHeight.Value; + scrollBar.Size *= 3; + } + }; + + var lblSize = new Label + { + Y = Pos.Bottom (rgOrientation), + Text = "Size:" + }; + view.Add (lblSize); + + NumericUpDown scrollSize = new () + { + Value = scrollBar.Size, + X = Pos.Right (lblSize) + 1, + Y = Pos.Top (lblSize) + }; + view.Add (scrollSize); + + scrollSize.ValueChanging += (s, e) => + { + if (e.NewValue < 0) + { + e.Cancel = true; + + return; + } + + if (scrollBar.Size != e.NewValue) + { + scrollBar.Size = e.NewValue; + } + }; + + var lblPosition = new Label + { + Y = Pos.Bottom (lblSize), + Text = "Position:" + }; + view.Add (lblPosition); + + NumericUpDown scrollPosition = new () + { + Value = scrollBar.Position, + X = Pos.Right (lblPosition) + 1, + Y = Pos.Top (lblPosition) + }; + view.Add (scrollPosition); + + scrollPosition.ValueChanging += (s, e) => + { + if (e.NewValue < 0) + { + e.Cancel = true; + + return; + } + + if (scrollBar.Position != e.NewValue) + { + scrollBar.Position = e.NewValue; + } + + if (scrollBar.Position != e.NewValue) + { + e.Cancel = true; + } + }; + + var lblSizeChanged = new Label + { + Y = Pos.Bottom (lblPosition) + 1 + }; + view.Add (lblSizeChanged); + + scrollBar.SizeChanged += (s, e) => + { + lblSizeChanged.Text = $"SizeChanged event - CurrentValue: {e.CurrentValue}"; + + if (scrollSize.Value != e.CurrentValue) + { + scrollSize.Value = e.CurrentValue; + } + }; + + var lblPosChanging = new Label + { + Y = Pos.Bottom (lblSizeChanged) + }; + view.Add (lblPosChanging); + + scrollBar.PositionChanging += (s, e) => { lblPosChanging.Text = $"PositionChanging event - CurrentValue: {e.CurrentValue}; NewValue: {e.NewValue}"; }; + + var lblPositionChanged = new Label + { + Y = Pos.Bottom (lblPosChanging) + }; + view.Add (lblPositionChanged); + + scrollBar.PositionChanged += (s, e) => + { + lblPositionChanged.Text = $"PositionChanged event - CurrentValue: {e.CurrentValue}"; + scrollPosition.Value = e.CurrentValue; + }; + + var lblScrollFrame = new Label + { + Y = Pos.Bottom (lblPositionChanged) + 1 + }; + view.Add (lblScrollFrame); + + var lblScrollViewport = new Label + { + Y = Pos.Bottom (lblScrollFrame) + }; + view.Add (lblScrollViewport); + + var lblScrollContentSize = new Label + { + Y = Pos.Bottom (lblScrollViewport) + }; + view.Add (lblScrollContentSize); + + + scrollBar.LayoutComplete += (s, e) => + { + lblScrollFrame.Text = $"ScrollBar Frame: {scrollBar.Frame.ToString ()}"; + lblScrollViewport.Text = $"ScrollBar Viewport: {scrollBar.Viewport.ToString ()}"; + lblScrollContentSize.Text = $"ScrollBar ContentSize: {scrollBar.GetContentSize ().ToString ()}"; + }; + + editor.Initialized += (s, e) => + { + scrollBar.Size = int.Max (app.GetContentSize ().Height * 2, app.GetContentSize ().Width * 2); + editor.ViewToEdit = scrollBar; + }; + + app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags; + + Application.Run (app); + app.Dispose (); + Application.Shutdown (); + } +} From 14ed92ef6ee66ea6182bc37995c1ef0af6a1dd8c Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 23 Aug 2024 17:04:32 +0100 Subject: [PATCH 036/128] Restore color scheme on mouse leave no matter the button. --- Terminal.Gui/Views/Scroll/ScrollButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollButton.cs b/Terminal.Gui/Views/Scroll/ScrollButton.cs index 589f6433e9..74f38c19e9 100644 --- a/Terminal.Gui/Views/Scroll/ScrollButton.cs +++ b/Terminal.Gui/Views/Scroll/ScrollButton.cs @@ -115,7 +115,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) /// protected internal override bool OnMouseLeave (MouseEvent mouseEvent) { - if (_savedColorScheme is { } && !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) + if (_savedColorScheme is { }) { ColorScheme = _savedColorScheme; _savedColorScheme = null; From cb7bc94c74ce0cdc7923b7951b65a315eae94dfd Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 24 Aug 2024 17:28:35 +0100 Subject: [PATCH 037/128] Ensures accurate calculation if is hosted by a ScrollBar before initialization. --- Terminal.Gui/Views/Scroll/Scroll.cs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 230008797e..2f77fc670d 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -21,8 +21,18 @@ public Scroll (ScrollBar? host) WantContinuousButtonPressed = true; CanFocus = false; Orientation = Orientation.Vertical; - Width = Dim.Auto (DimAutoStyle.Content, 1); - Height = Dim.Auto (DimAutoStyle.Content, 1); + + if (_host is { }) + { + Y = 1; + Width = Dim.Fill (); + Height = Dim.Fill (1); + } + else + { + Width = Dim.Auto (DimAutoStyle.Content, 1); + Height = Dim.Auto (DimAutoStyle.Content, 1); + } } @@ -68,6 +78,12 @@ public int Position return; } + if (_host is { IsInitialized: false }) + { + // Ensures a more exactly calculation + SetRelativeLayout (_host.Frame.Size); + } + int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; if (value + barSize > Size) From d47188f1f0717215248a16d5c165b9200eccab9a Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 25 Aug 2024 20:55:09 +0100 Subject: [PATCH 038/128] Consolidating GetPositionFromSliderLocation and GetSliderLocationDimensionFromPosition methods. --- Terminal.Gui/Views/Scroll/Scroll.cs | 7 +- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 19 ++-- UnitTests/Views/ScrollSliderTests.cs | 100 ++++++++++++++++++++++ 3 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 UnitTests/Views/ScrollSliderTests.cs diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 2f77fc670d..f96cc200ca 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -39,7 +39,7 @@ public Scroll (ScrollBar? host) internal readonly ScrollBar? _host; internal bool _wasSliderLayoutComplete = true; - private readonly ScrollSlider _slider; + internal readonly ScrollSlider _slider; private Orientation _orientation; private int _position; private int _size; @@ -100,10 +100,7 @@ public int Position _position = value; - if (!_slider._wasSliderMouse) - { - AdjustScroll (); - } + AdjustScroll (); OnPositionChanged (_position); } diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 075fa3c9e7..32032e3039 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -13,8 +13,6 @@ public ScrollSlider (Scroll host) WantMousePositionReports = true; } - internal bool _wasSliderMouse; - private readonly Scroll _host; private int _lastLocation = -1; private ColorScheme? _savedColorScheme; @@ -93,8 +91,6 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) } else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - _wasSliderMouse = true; - if (_host.Orientation == Orientation.Vertical) { Y = Frame.Y + offset < 0 ? 0 : @@ -157,7 +153,7 @@ internal override void OnLayoutComplete (LayoutEventArgs args) _host._wasSliderLayoutComplete = true; } - private int GetPositionFromSliderLocation (int location) + internal int GetPositionFromSliderLocation (int location) { if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0) { @@ -168,17 +164,16 @@ private int GetPositionFromSliderLocation (int location) // 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 - if ((_host.Orientation == Orientation.Vertical && location + Frame.Height == scrollSize) - || (_host.Orientation == Orientation.Horizontal && location + Frame.Width == scrollSize)) + if ((_host.Orientation == Orientation.Vertical && location + Frame.Height >= scrollSize) + || (_host.Orientation == Orientation.Horizontal && location + Frame.Width >= scrollSize)) { - return _host.Size - scrollSize; + return _host.Size - scrollSize; } - return Math.Min ((location * _host.Size + location) / scrollSize, _host.Size - scrollSize); + return (int)Math.Min (Math.Round ((double)(location * _host.Size + location) / scrollSize), _host.Size - scrollSize); } - // QUESTION: This method is only called from one place. Should it be inlined? Or, should it be made internal and unit tests be provided? - private (int Location, int Dimension) GetSliderLocationDimensionFromPosition () + internal (int Location, int Dimension) GetSliderLocationDimensionFromPosition () { if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0) { @@ -199,7 +194,7 @@ private int GetPositionFromSliderLocation (int location) _host.Position = _host.Size - scrollSize; } - location = Math.Min ((_host.Position * scrollSize + _host.Position) / _host.Size, scrollSize - dimension); + location = (int)Math.Min (Math.Round ((double)_host.Position * scrollSize / (_host.Size + 1)), scrollSize - dimension); if (_host.Position == _host.Size - scrollSize && location + dimension < scrollSize) { diff --git a/UnitTests/Views/ScrollSliderTests.cs b/UnitTests/Views/ScrollSliderTests.cs new file mode 100644 index 0000000000..9e0f757021 --- /dev/null +++ b/UnitTests/Views/ScrollSliderTests.cs @@ -0,0 +1,100 @@ +namespace Terminal.Gui.ViewsTests; + +public class ScrollSliderTests +{ + // Test for GetPositionFromSliderLocation to GetSliderLocationDimensionFromPosition + [Theory] + [InlineData (Orientation.Vertical, 26, 236, -1, 0)] + [InlineData (Orientation.Vertical, 26, 236, 0, 0)] + [InlineData (Orientation.Vertical, 26, 236, 5, 46)] + [InlineData (Orientation.Vertical, 26, 236, 10, 91)] + [InlineData (Orientation.Vertical, 26, 236, 15, 137)] + [InlineData (Orientation.Vertical, 26, 236, 20, 182)] + [InlineData (Orientation.Vertical, 26, 236, 26, 210)] + [InlineData (Orientation.Vertical, 26, 236, 27, 210)] + [InlineData (Orientation.Vertical, 37, 236, 2, 13)] + [InlineData (Orientation.Vertical, 42, 236, 29, 164)] + public void Test_Position_Location_Consistency (Orientation orientation, int scrollLength, int size, int location, int expectedPosition) + { + // Arrange + Scroll host = new () + { + Orientation = orientation, + Width = orientation == Orientation.Vertical ? 1 : scrollLength, + Height = orientation == Orientation.Vertical ? scrollLength : 1, + Size = size + }; + + host.BeginInit (); + host.EndInit (); + + // Act + host.Position = host._slider.GetPositionFromSliderLocation (location); + (int calculatedLocation, int calculatedDimension) = host._slider.GetSliderLocationDimensionFromPosition (); + int calculatedPosition = host._slider.GetPositionFromSliderLocation (calculatedLocation); + + // Assert + AssertLocation (scrollLength, location, calculatedLocation, calculatedDimension); + + Assert.Equal (host.Position, expectedPosition); + Assert.Equal (calculatedPosition, expectedPosition); + } + + // Randomized Test for more extensive testing + [Theory] + [InlineData (Orientation.Vertical, 26, 236, 5)] + public void Test_Position_Location_Consistency_Random (Orientation orientation, int scrollLength, int size, int testCount) + { + var random = new Random (); + + Scroll host = new () + { + Orientation = orientation, + Width = orientation == Orientation.Vertical ? 1 : scrollLength, + Height = orientation == Orientation.Vertical ? scrollLength : 1, + Size = size + }; + + host.BeginInit (); + host.EndInit (); + + // Number of random tests to run + for (var i = 0; i < testCount; i++) + { + // Arrange + int randomScrollLength = random.Next (0, 60); // Random content size length + int randomLocation = random.Next (0, randomScrollLength); // Random location + + host.Width = host.Orientation == Orientation.Vertical ? 1 : randomScrollLength; + host.Height = host.Orientation == Orientation.Vertical ? randomScrollLength : 1; + // Slider may have changed content size + host.LayoutSubviews (); + + // Act + host.Position = host._slider.GetPositionFromSliderLocation (randomLocation); + (int calculatedLocation, int calculatedDimension) = host._slider.GetSliderLocationDimensionFromPosition (); + int calculatedPosition = host._slider.GetPositionFromSliderLocation (calculatedLocation); + + // Assert + AssertLocation (randomScrollLength, randomLocation, calculatedLocation, calculatedDimension); + + Assert.Equal (host.Position, calculatedPosition); + } + } + + private static void AssertLocation (int scrollLength, int location, int calculatedLocation, int calculatedDimension) + { + if (location < 0) + { + Assert.Equal (0, calculatedLocation); + } + else if (location + calculatedDimension >= scrollLength) + { + Assert.Equal (scrollLength - calculatedDimension, calculatedLocation); + } + else + { + Assert.Equal (location, calculatedLocation); + } + } +} From 7a1eb9846e29e7119d36daf876064e77c482fa2b Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 25 Aug 2024 21:00:59 +0100 Subject: [PATCH 039/128] Code cleanup. --- Terminal.Gui/Views/Scroll/Scroll.cs | 4 ++-- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 18 +++++++++--------- UnitTests/Views/ScrollSliderTests.cs | 1 + 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index f96cc200ca..9be2495e1f 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -35,11 +35,10 @@ public Scroll (ScrollBar? host) } } - internal readonly ScrollBar? _host; - internal bool _wasSliderLayoutComplete = true; internal readonly ScrollSlider _slider; + internal bool _wasSliderLayoutComplete = true; private Orientation _orientation; private int _position; private int _size; @@ -173,6 +172,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) return _slider.OnMouseEvent (mouseEvent); } } + // Flag as false until slider layout is completed _wasSliderLayoutComplete = false; diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 32032e3039..01057cefa8 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -145,14 +145,6 @@ protected internal override bool OnMouseLeave (MouseEvent mouseEvent) return base.OnMouseLeave (mouseEvent); } - /// - internal override void OnLayoutComplete (LayoutEventArgs args) - { - base.OnLayoutComplete (args); - - _host._wasSliderLayoutComplete = true; - } - internal int GetPositionFromSliderLocation (int location) { if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0) @@ -167,7 +159,7 @@ internal int GetPositionFromSliderLocation (int location) if ((_host.Orientation == Orientation.Vertical && location + Frame.Height >= scrollSize) || (_host.Orientation == Orientation.Horizontal && location + Frame.Width >= scrollSize)) { - return _host.Size - scrollSize; + return _host.Size - scrollSize; } return (int)Math.Min (Math.Round ((double)(location * _host.Size + location) / scrollSize), _host.Size - scrollSize); @@ -210,6 +202,14 @@ internal int GetPositionFromSliderLocation (int location) return new (location, dimension); } + /// + internal override void OnLayoutComplete (LayoutEventArgs args) + { + base.OnLayoutComplete (args); + + _host._wasSliderLayoutComplete = true; + } + // TODO: I think you should create a new `internal` view named "ScrollSlider" with an `Orientation` property. It should inherit from View and override GetNormalColor and the mouse events // that can be moved within it's Superview, constrained to move only horizontally or vertically depending on Orientation. // This will really simplify a lot of this. diff --git a/UnitTests/Views/ScrollSliderTests.cs b/UnitTests/Views/ScrollSliderTests.cs index 9e0f757021..665f5a98a2 100644 --- a/UnitTests/Views/ScrollSliderTests.cs +++ b/UnitTests/Views/ScrollSliderTests.cs @@ -67,6 +67,7 @@ public void Test_Position_Location_Consistency_Random (Orientation orientation, host.Width = host.Orientation == Orientation.Vertical ? 1 : randomScrollLength; host.Height = host.Orientation == Orientation.Vertical ? randomScrollLength : 1; + // Slider may have changed content size host.LayoutSubviews (); From 71d558fd94766c37b90acb3d83eb106f19c53ca2 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 25 Aug 2024 21:56:14 +0100 Subject: [PATCH 040/128] Remove _wasSliderLayoutComplete field. --- Terminal.Gui/Views/Scroll/Scroll.cs | 19 ------------------- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 14 -------------- 2 files changed, 33 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 9be2495e1f..4e01a33c60 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -38,7 +38,6 @@ public Scroll (ScrollBar? host) internal readonly ScrollBar? _host; internal readonly ScrollSlider _slider; - internal bool _wasSliderLayoutComplete = true; private Orientation _orientation; private int _position; private int _size; @@ -134,12 +133,6 @@ public int Size /// protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { - if (!_wasSliderLayoutComplete) - { - // Do not process if slider layout wasn't yet completed - return base.OnMouseEvent (mouseEvent); - } - int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; @@ -173,21 +166,9 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) } } - // Flag as false until slider layout is completed - _wasSliderLayoutComplete = false; - return base.OnMouseEvent (mouseEvent); } - /// - protected internal override bool OnMouseLeave (MouseEvent mouseEvent) - { - // If scroll isn't handling mouse then reset the flag - _wasSliderLayoutComplete = true; - - return base.OnMouseLeave (mouseEvent); - } - // TODO: Move this into "ScrollSlider" and override it there. Scroll can then subscribe to _slider.LayoutComplete and call AdjustSlider. // QUESTION: I've been meaning to add a "View.FrameChanged" event (fired from LayoutComplete only if Frame has changed). Should we do that as part of this PR? // QUESTION: Note I *did* add "View.ViewportChanged" in a previous PR. diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 01057cefa8..99bfbe737f 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -71,12 +71,6 @@ public override Attribute GetNormalColor () /// protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { - if (!_host._wasSliderLayoutComplete) - { - // Ensure not blocking scroll mouse event - _host._wasSliderLayoutComplete = true; - } - int location = _host.Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; int offset = _lastLocation > -1 ? location - _lastLocation : 0; int barSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width; @@ -202,14 +196,6 @@ internal int GetPositionFromSliderLocation (int location) return new (location, dimension); } - /// - internal override void OnLayoutComplete (LayoutEventArgs args) - { - base.OnLayoutComplete (args); - - _host._wasSliderLayoutComplete = true; - } - // TODO: I think you should create a new `internal` view named "ScrollSlider" with an `Orientation` property. It should inherit from View and override GetNormalColor and the mouse events // that can be moved within it's Superview, constrained to move only horizontally or vertically depending on Orientation. // This will really simplify a lot of this. From d3ab81eb197b0765a88f513468ee94d50d81e09c Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 26 Aug 2024 00:13:30 +0100 Subject: [PATCH 041/128] Add ScrollBar unit tests and code cleanup. --- UnitTests/Views/ScrollBarTests.cs | 968 ++++++++++++++++++++++++++++++ UnitTests/Views/ScrollTests.cs | 7 +- 2 files changed, 972 insertions(+), 3 deletions(-) create mode 100644 UnitTests/Views/ScrollBarTests.cs diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs new file mode 100644 index 0000000000..39eb564682 --- /dev/null +++ b/UnitTests/Views/ScrollBarTests.cs @@ -0,0 +1,968 @@ +using Xunit.Abstractions; + +namespace Terminal.Gui.ViewsTests; + +public class ScrollBarTests +{ + public ScrollBarTests (ITestOutputHelper output) { _output = output; } + private readonly ITestOutputHelper _output; + + [Theory] + [AutoInitShutdown] + [InlineData ( + 20, + @" +▲ +█ +█ +█ +█ +░ +░ +░ +░ +▼", + @" +▲ +░ +░ +█ +█ +█ +█ +░ +░ +▼", + @" +▲ +░ +░ +░ +░ +█ +█ +█ +█ +▼", + @" +▲ +░ +░ +█ +█ +░ +░ +░ +░ +▼", + @" +◄████░░░░►", + @" +◄░░████░░►", + @" +◄░░░░████►", + @" +◄░░██░░░░►")] + [InlineData ( + 40, + @" +▲ +█ +█ +░ +░ +░ +░ +░ +░ +▼", + @" +▲ +░ +█ +█ +░ +░ +░ +░ +░ +▼", + @" +▲ +░ +░ +█ +█ +░ +░ +░ +░ +▼", + @" +▲ +░ +█ +░ +░ +░ +░ +░ +░ +▼", + @" +◄██░░░░░░►", + @" +◄░██░░░░░►", + @" +◄░░██░░░░►", + @" +◄░█░░░░░░►")] + public void Changing_Position_Size_Orientation_Draws_Correctly ( + int size, + string firstVertExpected, + string middleVertExpected, + string endVertExpected, + string sizeVertExpected, + string firstHoriExpected, + string middleHoriExpected, + string endHoriExpected, + string sizeHoriExpected + ) + { + var scrollBar = new ScrollBar + { + Orientation = Orientation.Vertical, + Size = size, + Height = 10 + }; + var top = new Toplevel (); + top.Add (scrollBar); + Application.Begin (top); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (firstVertExpected, _output); + + scrollBar.Position = 4; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (middleVertExpected, _output); + + scrollBar.Position = 10; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (endVertExpected, _output); + + scrollBar.Size = size * 2; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (sizeVertExpected, _output); + + scrollBar.Orientation = Orientation.Horizontal; + scrollBar.Width = 10; + scrollBar.Height = 1; + scrollBar.Position = 0; + scrollBar.Size = size; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (firstHoriExpected, _output); + + scrollBar.Position = 4; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (middleHoriExpected, _output); + + scrollBar.Position = 10; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (endHoriExpected, _output); + + scrollBar.Size = size * 2; + Application.Refresh (); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (sizeHoriExpected, _output); + } + + [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.Position); + } + + [Theory] + [AutoInitShutdown] + [InlineData ( + Orientation.Vertical, + 20, + 10, + 4, + @" +▲ +░ +░ +░ +░ +█ +█ +█ +█ +▼", + 2, + @" +▲ +░ +█ +█ +█ +█ +░ +░ +░ +▼")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 5, + @" +▲ +░ +░ +█ +█ +░ +░ +░ +░ +▼", + 18, + @" +▲ +░ +░ +░ +░ +█ +█ +░ +░ +▼")] + [InlineData ( + Orientation.Horizontal, + 20, + 10, + 4, + @" +◄░░░░████►", + 2, + @" +◄░████░░░►")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 5, + @" +◄░░██░░░░►", + 18, + @" +◄░░░░██░░►")] + public void Mouse_On_The_Container (Orientation orientation, int size, int position, int location, string output, int expectedPos, string expectedOut) + { + var scrollBar = new ScrollBar + { + Width = orientation == Orientation.Vertical ? 1 : 10, + Height = orientation == Orientation.Vertical ? 10 : 1, + Orientation = orientation, Size = size, + Position = position + }; + var top = new Toplevel (); + top.Add (scrollBar); + Application.Begin (top); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (output, _output); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, location) : new Point (location, 0), + Flags = MouseFlags.Button1Pressed + }); + Assert.Equal (expectedPos, scrollBar.Position); + + Application.Refresh (); + _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); + } + + [Theory] + [AutoInitShutdown] + [InlineData ( + Orientation.Vertical, + 20, + 10, + 5, + 5, + @" +▲ +░ +░ +░ +░ +█ +█ +█ +█ +▼", + MouseFlags.Button1Pressed, + 10, + @" +▲ +░ +░ +░ +░ +█ +█ +█ +█ +▼")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 3, + 3, + @" +▲ +░ +░ +█ +█ +░ +░ +░ +░ +▼", + MouseFlags.Button1Pressed, + 10, + @" +▲ +░ +░ +█ +█ +░ +░ +░ +░ +▼")] + [InlineData ( + Orientation.Horizontal, + 20, + 10, + 5, + 5, + @" +◄░░░░████►", + MouseFlags.Button1Pressed, + 10, + @" +◄░░░░████►")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 3, + 3, + @" +◄░░██░░░░►", + MouseFlags.Button1Pressed, + 10, + @" +◄░░██░░░░►")] + [InlineData ( + Orientation.Vertical, + 20, + 10, + 5, + 7, + @" +▲ +░ +░ +░ +░ +█ +█ +█ +█ +▼", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 12, + @" +▲ +░ +░ +░ +░ +█ +█ +█ +█ +▼")] + [InlineData ( + Orientation.Horizontal, + 20, + 10, + 5, + 4, + @" +◄░░░░████►", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 8, + @" +◄░░░████░►")] + [InlineData ( + Orientation.Vertical, + 20, + 10, + 5, + 6, + @" +▲ +░ +░ +░ +░ +█ +█ +█ +█ +▼", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 12, + @" +▲ +░ +░ +░ +░ +█ +█ +█ +█ +▼")] + [InlineData ( + Orientation.Horizontal, + 20, + 10, + 5, + 6, + @" +◄░░░░████►", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 12, + @" +◄░░░░████►")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 2, + 1, + @" +▲ +░ +░ +█ +█ +░ +░ +░ +░ +▼", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 2, + @" +▲ +█ +█ +░ +░ +░ +░ +░ +░ +▼")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 2, + 1, + @" +◄░░██░░░░►", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 2, + @" +◄██░░░░░░►")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 3, + 4, + @" +▲ +░ +░ +█ +█ +░ +░ +░ +░ +▼", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 15, + @" +▲ +░ +░ +░ +█ +█ +░ +░ +░ +▼")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 3, + 4, + @" +◄░░██░░░░►", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 15, + @" +◄░░░██░░░►")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 3, + 3, + @" +▲ +░ +░ +█ +█ +░ +░ +░ +░ +▼", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 10, + @" +▲ +░ +░ +█ +█ +░ +░ +░ +░ +▼")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 3, + 3, + @" +◄░░██░░░░►", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 10, + @" +◄░░██░░░░►")] + [InlineData ( + Orientation.Vertical, + 40, + 10, + 3, + 5, + @" +▲ +░ +░ +█ +█ +░ +░ +░ +░ +▼", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 20, + @" +▲ +░ +░ +░ +░ +█ +█ +░ +░ +▼")] + [InlineData ( + Orientation.Horizontal, + 40, + 10, + 3, + 5, + @" +◄░░██░░░░►", + MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, + 20, + @" +◄░░░░██░░►")] + public void Mouse_On_The_Slider ( + Orientation orientation, + int size, + int position, + int startLocation, + int endLocation, + string output, + MouseFlags mouseFlags, + int expectedPos, + string expectedOut + ) + { + var scrollBar = new ScrollBar + { + Width = orientation == Orientation.Vertical ? 1 : 10, + Height = orientation == Orientation.Vertical ? 10 : 1, + Orientation = orientation, + Size = size, Position = position + }; + var top = new Toplevel (); + top.Add (scrollBar); + Application.Begin (top); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (output, _output); + + Assert.Null (Application.MouseGrabView); + + if (mouseFlags.HasFlag (MouseFlags.ReportMousePosition)) + { + MouseFlags mf = mouseFlags & ~MouseFlags.ReportMousePosition; + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), + Flags = mf + }); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, endLocation) : new (endLocation, 0), + Flags = mouseFlags + }); + } + else + { + Assert.Equal (startLocation, endLocation); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), + Flags = mouseFlags + }); + } + + Assert.Equal ("scrollSlider", Application.MouseGrabView?.Id); + Assert.Equal (expectedPos, scrollBar.Position); + + Application.Refresh (); + _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), + Flags = MouseFlags.Button1Released + }); + Assert.Null (Application.MouseGrabView); + } + + [Theory] + [AutoInitShutdown] + [InlineData (Orientation.Vertical)] + [InlineData (Orientation.Horizontal)] + public void Moving_Mouse_Outside_Host_Ensures_Correct_Location (Orientation orientation) + { + var scrollBar = new ScrollBar + { + X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, + Position = 5, Orientation = orientation + }; + var top = new Toplevel (); + top.Add (scrollBar); + Application.Begin (top); + + var scroll = (Scroll)scrollBar.Subviews.FirstOrDefault (x => x is Scroll); + Rectangle scrollSliderFrame = scroll!.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame; + Assert.Equal (scrollSliderFrame, orientation == Orientation.Vertical ? new (0, 2, 1, 4) : new (2, 0, 4, 1)); + + Application.OnMouseEvent (new () { Position = orientation == Orientation.Vertical ? new (10, 14) : new (14, 10), Flags = MouseFlags.Button1Pressed }); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (10, 0) : new (0, 10), + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + }); + Assert.Equal (new (0, 0), scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame.Location); + + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (0, 25) : new (80, 0), + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + }); + + Assert.Equal ( + orientation == Orientation.Vertical ? new (0, 4) : new (4, 0), + scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame.Location); + } + + [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) + { + var scrollBar = new ScrollBar { Orientation = orientation, Height = 10, Size = size }; + Assert.Equal (0, scrollBar.Position); + + scrollBar.Position = -1; + Assert.Equal (0, scrollBar.Position); + + scrollBar.Position = size; + Assert.Equal (0, scrollBar.Position); + + scrollBar.Position = expectedPos; + Assert.Equal (expectedPos, scrollBar.Position); + } + + [Fact] + public void PositionChanging_Cancelable_And_PositionChanged_Events () + { + var changingCount = 0; + var changedCount = 0; + var scrollBar = new ScrollBar { Size = 10 }; + + scrollBar.PositionChanging += (s, e) => + { + if (changingCount == 0) + { + e.Cancel = true; + } + + changingCount++; + }; + scrollBar.PositionChanged += (s, e) => changedCount++; + + scrollBar.Position = 1; + Assert.Equal (0, scrollBar.Position); + Assert.Equal (1, changingCount); + Assert.Equal (0, changedCount); + + scrollBar.Position = 1; + Assert.Equal (1, scrollBar.Position); + Assert.Equal (2, changingCount); + Assert.Equal (1, changedCount); + } + + [Fact] + public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position_Was_Really_Changed () + { + var changing = 0; + var cancel = false; + var changed = 0; + var scrollBar = new ScrollBar { Height = 10, Size = 20 }; + scrollBar.PositionChanging += Scroll_PositionChanging; + scrollBar.PositionChanged += Scroll_PositionChanged; + + Assert.Equal (Orientation.Vertical, scrollBar.Orientation); + Assert.Equal (new (0, 0, 1, 10), scrollBar.Viewport); + Assert.Equal (0, scrollBar.Position); + Assert.Equal (0, changing); + Assert.Equal (0, changed); + + scrollBar.Position = 0; + Assert.Equal (0, scrollBar.Position); + Assert.Equal (0, changing); + Assert.Equal (0, changed); + + scrollBar.Position = 1; + Assert.Equal (1, scrollBar.Position); + Assert.Equal (1, changing); + Assert.Equal (1, changed); + + Reset (); + cancel = true; + scrollBar.Position = 2; + Assert.Equal (1, scrollBar.Position); + Assert.Equal (1, changing); + Assert.Equal (0, changed); + + Reset (); + scrollBar.Position = 10; + Assert.Equal (10, scrollBar.Position); + Assert.Equal (1, changing); + Assert.Equal (1, changed); + + Reset (); + scrollBar.Position = 11; + Assert.Equal (11, scrollBar.Position); + Assert.Equal (1, changing); + Assert.Equal (1, changed); + + Reset (); + scrollBar.Position = 12; + Assert.Equal (12, scrollBar.Position); + Assert.Equal (1, changing); + Assert.Equal (1, changed); + + Reset (); + scrollBar.Position = 13; + Assert.Equal (12, scrollBar.Position); + Assert.Equal (0, changing); + Assert.Equal (0, changed); + + Reset (); + scrollBar.Position = 0; + Assert.Equal (0, scrollBar.Position); + Assert.Equal (1, changing); + Assert.Equal (1, changed); + + scrollBar.PositionChanging -= Scroll_PositionChanging; + scrollBar.PositionChanged -= Scroll_PositionChanged; + + void Scroll_PositionChanging (object sender, CancelEventArgs e) + { + changing++; + e.Cancel = cancel; + } + + void Scroll_PositionChanged (object sender, EventArgs e) { changed++; } + + void Reset () + { + changing = 0; + cancel = false; + changed = 0; + } + } + + [Fact] + public void SizeChanged_Event () + { + var count = 0; + var scrollBar = new ScrollBar (); + scrollBar.SizeChanged += (s, e) => count++; + + scrollBar.Size = 10; + Assert.Equal (10, scrollBar.Size); + Assert.Equal (1, count); + } + + [Theory] + [AutoInitShutdown] + [InlineData ( + 3, + 10, + 1, + Orientation.Vertical, + @" +┌─┐ +│▲│ +│█│ +│█│ +│░│ +│░│ +│░│ +│░│ +│▼│ +└─┘")] + [InlineData ( + 10, + 3, + 1, + Orientation.Horizontal, + @" +┌────────┐ +│◄██░░░░►│ +└────────┘")] + [InlineData ( + 3, + 10, + 3, + Orientation.Vertical, + @" +┌───┐ +│ ▲ │ +│███│ +│███│ +│░░░│ +│░░░│ +│░░░│ +│░░░│ +│ ▼ │ +└───┘")] + [InlineData ( + 10, + 3, + 3, + Orientation.Horizontal, + @" +┌────────┐ +│ ██░░░░ │ +│◄██░░░░►│ +│ ██░░░░ │ +└────────┘")] + public void Vertical_Horizontal_Draws_Correctly (int sizeWidth, int sizeHeight, int widthHeight, Orientation orientation, string expected) + { + var super = new Window { Id = "super", Width = Dim.Fill (), Height = Dim.Fill () }; + var top = new Toplevel (); + top.Add (super); + + var scrollBar = new ScrollBar + { + Orientation = orientation, + Size = orientation == Orientation.Vertical ? sizeHeight * 2 : sizeWidth * 2, + Width = orientation == Orientation.Vertical ? widthHeight : Dim.Fill (), + Height = orientation == Orientation.Vertical ? Dim.Fill () : widthHeight + }; + super.Add (scrollBar); + + Application.Begin (top); + + ((FakeDriver)Application.Driver)!.SetBufferSize ( + sizeWidth + (orientation == Orientation.Vertical ? widthHeight - 1 : 0), + sizeHeight + (orientation == Orientation.Vertical ? 0 : widthHeight - 1)); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + } +} diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index de0e39e26e..ec36691151 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -710,6 +710,7 @@ string expectedOut [Theory] [AutoInitShutdown] [InlineData (Orientation.Vertical)] + [InlineData (Orientation.Horizontal)] public void Moving_Mouse_Outside_Host_Ensures_Correct_Location (Orientation orientation) { var scroll = new Scroll @@ -946,9 +947,9 @@ public void Vertical_Horizontal_Draws_Correctly (int sizeWidth, int sizeHeight, Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize ( - sizeWidth + (orientation == Orientation.Vertical ? widthHeight - 1 : 0), - sizeHeight + (orientation == Orientation.Vertical ? 0 : widthHeight - 1)); + ((FakeDriver)Application.Driver)!.SetBufferSize ( + sizeWidth + (orientation == Orientation.Vertical ? widthHeight - 1 : 0), + sizeHeight + (orientation == Orientation.Vertical ? 0 : widthHeight - 1)); _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); } From a343fd0089c0ada0c5b9c07acc16ba91e667719a Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 26 Aug 2024 01:06:06 +0100 Subject: [PATCH 042/128] Add ScrollButton unit test and code cleanup. --- UnitTests/Views/ScrollBarTests.cs | 62 +++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs index 39eb564682..12129eceb1 100644 --- a/UnitTests/Views/ScrollBarTests.cs +++ b/UnitTests/Views/ScrollBarTests.cs @@ -692,6 +692,7 @@ string expectedOut } Assert.Equal ("scrollSlider", Application.MouseGrabView?.Id); + Assert.IsType (Application.MouseGrabView); Assert.Equal (expectedPos, scrollBar.Position); Application.Refresh (); @@ -722,7 +723,7 @@ public void Moving_Mouse_Outside_Host_Ensures_Correct_Location (Orientation orie Application.Begin (top); var scroll = (Scroll)scrollBar.Subviews.FirstOrDefault (x => x is Scroll); - Rectangle scrollSliderFrame = scroll!.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame; + Rectangle scrollSliderFrame = scroll!.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame; Assert.Equal (scrollSliderFrame, orientation == Orientation.Vertical ? new (0, 2, 1, 4) : new (2, 0, 4, 1)); Application.OnMouseEvent (new () { Position = orientation == Orientation.Vertical ? new (10, 14) : new (14, 10), Flags = MouseFlags.Button1Pressed }); @@ -733,7 +734,7 @@ public void Moving_Mouse_Outside_Host_Ensures_Correct_Location (Orientation orie Position = orientation == Orientation.Vertical ? new (10, 0) : new (0, 10), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); - Assert.Equal (new (0, 0), scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame.Location); + Assert.Equal (new (0, 0), scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); Application.OnMouseEvent ( new () @@ -744,7 +745,7 @@ public void Moving_Mouse_Outside_Host_Ensures_Correct_Location (Orientation orie Assert.Equal ( orientation == Orientation.Vertical ? new (0, 4) : new (4, 0), - scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame.Location); + scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); } [Theory] @@ -965,4 +966,59 @@ public void Vertical_Horizontal_Draws_Correctly (int sizeWidth, int sizeHeight, _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); } + + [Theory] + [AutoInitShutdown] + [InlineData (Orientation.Vertical)] + [InlineData (Orientation.Horizontal)] + public void Mouse_Pressed_On_ScrollButton_Changes_Position (Orientation orientation) + { + var scrollBar = new ScrollBar + { + X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, + Orientation = orientation + }; + var top = new Toplevel (); + top.Add (scrollBar); + Application.Begin (top); + + var scroll = (Scroll)scrollBar.Subviews.FirstOrDefault (x => x is Scroll); + Rectangle scrollSliderFrame = scroll!.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame; + Assert.Equal (scrollSliderFrame, orientation == Orientation.Vertical ? new (0, 0, 1, 4) : new (0, 0, 4, 1)); + Assert.Equal (0, scrollBar.Position); + + // ScrollButton increase + for (int i = 0; i < 13; i++) + { + Application.OnMouseEvent (new () { Position = orientation == Orientation.Vertical ? new (10, 19) : new (19, 10), Flags = MouseFlags.Button1Pressed }); + + if (i < 12) + { + Assert.Equal (i + 1, scrollBar.Position); + } + else + { + Assert.Equal (i, scrollBar.Position); + Assert.Equal ( + orientation == Orientation.Vertical ? new (0, 4) : new (4, 0), + scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); + } + + } + + for (int i = 12; i > -1; i--) + { + Application.OnMouseEvent (new () { Position = new (10, 10), Flags = MouseFlags.Button1Pressed }); + + if (i > 0) + { + Assert.Equal (i - 1, scrollBar.Position); + } + else + { + Assert.Equal (0, scrollBar.Position); + Assert.Equal (new (0, 0), scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); + } + } + } } From ae3fcef17a44ef1797ab54c1e1ba73b8512bd813 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 26 Aug 2024 12:47:23 +0100 Subject: [PATCH 043/128] Remove host parameter from scroll classes. --- Terminal.Gui/Views/Scroll/Scroll.cs | 30 +++------ Terminal.Gui/Views/Scroll/ScrollBar.cs | 9 ++- Terminal.Gui/Views/Scroll/ScrollButton.cs | 31 +++++----- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 74 +++++++++++------------ UnitTests/Views/ScrollSliderTests.cs | 34 +++++------ 5 files changed, 84 insertions(+), 94 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 4e01a33c60..28d2037fed 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -10,33 +10,19 @@ namespace Terminal.Gui; public class Scroll : View { /// - public Scroll () : this (null) { } - - public Scroll (ScrollBar? host) + public Scroll () { - _host = host; - _slider = new (this); + _slider = new (); Add (_slider); WantContinuousButtonPressed = true; CanFocus = false; Orientation = Orientation.Vertical; - if (_host is { }) - { - Y = 1; - Width = Dim.Fill (); - Height = Dim.Fill (1); - } - else - { - Width = Dim.Auto (DimAutoStyle.Content, 1); - Height = Dim.Auto (DimAutoStyle.Content, 1); - } + Width = Dim.Auto (DimAutoStyle.Content, 1); + Height = Dim.Auto (DimAutoStyle.Content, 1); } - internal readonly ScrollBar? _host; - internal readonly ScrollSlider _slider; private Orientation _orientation; private int _position; @@ -76,10 +62,10 @@ public int Position return; } - if (_host is { IsInitialized: false }) + if (SupView is { IsInitialized: false }) { // Ensures a more exactly calculation - SetRelativeLayout (_host.Frame.Size); + SetRelativeLayout (SupView.Frame.Size); } int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; @@ -193,7 +179,7 @@ protected virtual CancelEventArgs OnPositionChanging (int currentPos, int n internal void AdjustScroll () { - if (_host is { }) + if (SupView is { }) { X = Orientation == Orientation.Vertical ? 0 : 1; Y = Orientation == Orientation.Vertical ? 1 : 0; @@ -213,6 +199,8 @@ internal override void OnLayoutComplete (LayoutEventArgs args) AdjustScroll (); } + internal ScrollBar? SupView => SuperView as ScrollBar; + private void SetScrollText () { TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index 39f3cf2025..5ebfa15ab6 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -17,9 +17,9 @@ public class ScrollBar : View /// public ScrollBar () { - _scroll = new (this); - _decrease = new (this); - _increase = new (this, VariationMode.Increase); + _scroll = new (); + _decrease = new (); + _increase = new () { VariationMode = VariationMode.Increase }; Add (_scroll, _decrease, _increase); CanFocus = false; @@ -68,6 +68,9 @@ public int Position /// public event EventHandler>? PositionChanging; + /// + /// Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through. + /// public int Size { get => _scroll.Size; diff --git a/Terminal.Gui/Views/Scroll/ScrollButton.cs b/Terminal.Gui/Views/Scroll/ScrollButton.cs index 74f38c19e9..d9064388c8 100644 --- a/Terminal.Gui/Views/Scroll/ScrollButton.cs +++ b/Terminal.Gui/Views/Scroll/ScrollButton.cs @@ -10,10 +10,8 @@ internal enum VariationMode internal class ScrollButton : View { - public ScrollButton (ScrollBar host, VariationMode variation = VariationMode.Decrease) + public ScrollButton () { - _host = host; - VariationMode = variation; TextAlignment = Alignment.Center; VerticalTextAlignment = Alignment.Center; Id = "scrollButton"; @@ -23,7 +21,6 @@ public ScrollButton (ScrollBar host, VariationMode variation = VariationMode.Dec WantContinuousButtonPressed = true; } - private readonly ScrollBar _host; private ColorScheme? _savedColorScheme; public void AdjustButton () @@ -33,8 +30,8 @@ public void AdjustButton () return; } - Width = _host.Orientation == Orientation.Vertical ? Dim.Fill () : 1; - Height = _host.Orientation == Orientation.Vertical ? 1 : Dim.Fill (); + Width = SupView.Orientation == Orientation.Vertical ? Dim.Fill () : 1; + Height = SupView.Orientation == Orientation.Vertical ? 1 : Dim.Fill (); switch (VariationMode) { @@ -44,8 +41,8 @@ public void AdjustButton () break; case VariationMode.Increase: - X = _host.Orientation == Orientation.Vertical ? 0 : Pos.AnchorEnd (1); - Y = _host.Orientation == Orientation.Vertical ? Pos.AnchorEnd (1) : 0; + X = SupView.Orientation == Orientation.Vertical ? 0 : Pos.AnchorEnd (1); + Y = SupView.Orientation == Orientation.Vertical ? Pos.AnchorEnd (1) : 0; break; default: @@ -60,22 +57,22 @@ public override Attribute GetNormalColor () { if (_savedColorScheme is null) { - ColorScheme = new () { Normal = new (_host.ColorScheme.HotNormal.Foreground, _host.ColorScheme.HotNormal.Background) }; + ColorScheme = new () { Normal = new (SupView.ColorScheme.HotNormal.Foreground, SupView.ColorScheme.HotNormal.Background) }; } else { - ColorScheme = new () { Normal = new (_host.ColorScheme.Normal.Background, _host.ColorScheme.Normal.Foreground) }; + ColorScheme = new () { Normal = new (SupView.ColorScheme.Normal.Background, SupView.ColorScheme.Normal.Foreground) }; } return base.GetNormalColor (); } - public VariationMode VariationMode { get; } + public VariationMode VariationMode { get; init; } /// protected internal override bool? OnMouseEnter (MouseEvent mouseEvent) { - _savedColorScheme ??= _host.ColorScheme; + _savedColorScheme ??= SupView.ColorScheme; ColorScheme = new () { @@ -97,11 +94,11 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) switch (VariationMode) { case VariationMode.Decrease: - _host.Position--; + SupView.Position--; return true; case VariationMode.Increase: - _host.Position++; + SupView.Position++; return true; default: @@ -129,15 +126,17 @@ private void SetButtonText () switch (VariationMode) { case VariationMode.Decrease: - Text = _host.Orientation == Orientation.Vertical ? Glyphs.UpArrow.ToString () : Glyphs.LeftArrow.ToString (); + Text = SupView.Orientation == Orientation.Vertical ? Glyphs.UpArrow.ToString () : Glyphs.LeftArrow.ToString (); break; case VariationMode.Increase: - Text = _host.Orientation == Orientation.Vertical ? Glyphs.DownArrow.ToString () : Glyphs.RightArrow.ToString (); + Text = SupView.Orientation == Orientation.Vertical ? Glyphs.DownArrow.ToString () : Glyphs.RightArrow.ToString (); break; default: throw new ArgumentOutOfRangeException (); } } + + private ScrollBar SupView => (SuperView as ScrollBar)!; } diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 99bfbe737f..0e640354be 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -4,16 +4,14 @@ namespace Terminal.Gui; internal class ScrollSlider : View { - public ScrollSlider (Scroll host) + public ScrollSlider () { - _host = host; Id = "scrollSlider"; Width = Dim.Auto (DimAutoStyle.Content); Height = Dim.Auto (DimAutoStyle.Content); WantMousePositionReports = true; } - private readonly Scroll _host; private int _lastLocation = -1; private ColorScheme? _savedColorScheme; @@ -25,13 +23,13 @@ public void AdjustSlider () } (int Location, int Dimension) sliderLocationAndDimension = GetSliderLocationDimensionFromPosition (); - X = _host.Orientation == Orientation.Vertical ? 0 : sliderLocationAndDimension.Location; - Y = _host.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Location : 0; + X = SupView.Orientation == Orientation.Vertical ? 0 : sliderLocationAndDimension.Location; + Y = SupView.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Location : 0; SetContentSize ( new ( - _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Width : sliderLocationAndDimension.Dimension, - _host.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Dimension : _host.GetContentSize ().Height + SupView.Orientation == Orientation.Vertical ? SupView.GetContentSize ().Width : sliderLocationAndDimension.Dimension, + SupView.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Dimension : SupView.GetContentSize ().Height )); SetSliderText (); } @@ -41,11 +39,11 @@ public override Attribute GetNormalColor () { if (_savedColorScheme is null) { - ColorScheme = new () { Normal = new (_host.ColorScheme.HotNormal.Foreground, _host.ColorScheme.HotNormal.Foreground) }; + ColorScheme = new () { Normal = new (SupView.ColorScheme.HotNormal.Foreground, SupView.ColorScheme.HotNormal.Foreground) }; } else { - ColorScheme = new () { Normal = new (_host.ColorScheme.Normal.Foreground, _host.ColorScheme.Normal.Foreground) }; + ColorScheme = new () { Normal = new (SupView.ColorScheme.Normal.Foreground, SupView.ColorScheme.Normal.Foreground) }; } return base.GetNormalColor (); @@ -54,7 +52,7 @@ public override Attribute GetNormalColor () /// protected internal override bool? OnMouseEnter (MouseEvent mouseEvent) { - _savedColorScheme ??= _host.ColorScheme; + _savedColorScheme ??= SupView.ColorScheme; ColorScheme = new () { @@ -71,9 +69,9 @@ public override Attribute GetNormalColor () /// protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { - int location = _host.Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; + int location = SupView.Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; int offset = _lastLocation > -1 ? location - _lastLocation : 0; - int barSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width; + int barSize = SupView.Orientation == Orientation.Vertical ? SupView.GetContentSize ().Height : SupView.GetContentSize ().Width; if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && _lastLocation == -1) { @@ -85,19 +83,19 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) } else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - if (_host.Orientation == Orientation.Vertical) + if (SupView.Orientation == Orientation.Vertical) { Y = Frame.Y + offset < 0 ? 0 : Frame.Y + offset + Frame.Height > barSize ? Math.Max (barSize - Frame.Height, 0) : Frame.Y + offset; - _host.Position = GetPositionFromSliderLocation (Frame.Y); + SupView.Position = GetPositionFromSliderLocation (Frame.Y); } else { X = Frame.X + offset < 0 ? 0 : Frame.X + offset + Frame.Width > barSize ? Math.Max (barSize - Frame.Width, 0) : Frame.X + offset; - _host.Position = GetPositionFromSliderLocation (Frame.X); + SupView.Position = GetPositionFromSliderLocation (Frame.X); } } else if (mouseEvent.Flags == MouseFlags.Button1Released) @@ -109,15 +107,15 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) Application.UngrabMouse (); } } - else if ((mouseEvent.Flags == MouseFlags.WheeledDown && _host.Orientation == Orientation.Vertical) - || (mouseEvent.Flags == MouseFlags.WheeledRight && _host.Orientation == Orientation.Horizontal)) + else if ((mouseEvent.Flags == MouseFlags.WheeledDown && SupView.Orientation == Orientation.Vertical) + || (mouseEvent.Flags == MouseFlags.WheeledRight && SupView.Orientation == Orientation.Horizontal)) { - _host.Position = Math.Min (_host.Position + 1, _host.Size - barSize); + SupView.Position = Math.Min (SupView.Position + 1, SupView.Size - barSize); } - else if ((mouseEvent.Flags == MouseFlags.WheeledUp && _host.Orientation == Orientation.Vertical) - || (mouseEvent.Flags == MouseFlags.WheeledLeft && _host.Orientation == Orientation.Horizontal)) + else if ((mouseEvent.Flags == MouseFlags.WheeledUp && SupView.Orientation == Orientation.Vertical) + || (mouseEvent.Flags == MouseFlags.WheeledLeft && SupView.Orientation == Orientation.Horizontal)) { - _host.Position = Math.Max (_host.Position - 1, 0); + SupView.Position = Math.Max (SupView.Position - 1, 0); } else if (mouseEvent.Flags != MouseFlags.ReportMousePosition) { @@ -141,48 +139,48 @@ protected internal override bool OnMouseLeave (MouseEvent mouseEvent) internal int GetPositionFromSliderLocation (int location) { - if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0) + if (SupView.GetContentSize ().Height == 0 || SupView.GetContentSize ().Width == 0) { return 0; } - int scrollSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width; + int scrollSize = SupView.Orientation == Orientation.Vertical ? SupView.GetContentSize ().Height : SupView.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 - if ((_host.Orientation == Orientation.Vertical && location + Frame.Height >= scrollSize) - || (_host.Orientation == Orientation.Horizontal && location + Frame.Width >= scrollSize)) + if ((SupView.Orientation == Orientation.Vertical && location + Frame.Height >= scrollSize) + || (SupView.Orientation == Orientation.Horizontal && location + Frame.Width >= scrollSize)) { - return _host.Size - scrollSize; + return SupView.Size - scrollSize; } - return (int)Math.Min (Math.Round ((double)(location * _host.Size + location) / scrollSize), _host.Size - scrollSize); + return (int)Math.Min (Math.Round ((double)(location * SupView.Size + location) / scrollSize), SupView.Size - scrollSize); } internal (int Location, int Dimension) GetSliderLocationDimensionFromPosition () { - if (_host.GetContentSize ().Height == 0 || _host.GetContentSize ().Width == 0) + if (SupView.GetContentSize ().Height == 0 || SupView.GetContentSize ().Width == 0) { return new (0, 0); } - int scrollSize = _host.Orientation == Orientation.Vertical ? _host.GetContentSize ().Height : _host.GetContentSize ().Width; + int scrollSize = SupView.Orientation == Orientation.Vertical ? SupView.GetContentSize ().Height : SupView.GetContentSize ().Width; int location; int dimension; - if (_host.Size > 0) + if (SupView.Size > 0) { - dimension = (int)Math.Min (Math.Max (Math.Ceiling ((double)scrollSize * scrollSize / _host.Size), 1), scrollSize); + dimension = (int)Math.Min (Math.Max (Math.Ceiling ((double)scrollSize * scrollSize / SupView.Size), 1), scrollSize); // Ensure the Position is valid - if (_host.Position > 0 && _host.Position + scrollSize > _host.Size) + if (SupView.Position > 0 && SupView.Position + scrollSize > SupView.Size) { - _host.Position = _host.Size - scrollSize; + SupView.Position = SupView.Size - scrollSize; } - location = (int)Math.Min (Math.Round ((double)_host.Position * scrollSize / (_host.Size + 1)), scrollSize - dimension); + location = (int)Math.Min (Math.Round ((double)SupView.Position * scrollSize / (SupView.Size + 1)), scrollSize - dimension); - if (_host.Position == _host.Size - scrollSize && location + dimension < scrollSize) + if (SupView.Position == SupView.Size - scrollSize && location + dimension < scrollSize) { location = scrollSize - dimension; } @@ -202,12 +200,14 @@ internal int GetPositionFromSliderLocation (int location) private void SetSliderText () { - TextDirection = _host.Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; + TextDirection = SupView.Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; // QUESTION: Should these Glyphs be configurable via CM? Text = string.Concat ( Enumerable.Repeat ( Glyphs.ContinuousMeterSegment.ToString (), - _host.GetContentSize ().Width * _host.GetContentSize ().Height)); + SupView.GetContentSize ().Width * SupView.GetContentSize ().Height)); } + + private Scroll SupView => (SuperView as Scroll)!; } diff --git a/UnitTests/Views/ScrollSliderTests.cs b/UnitTests/Views/ScrollSliderTests.cs index 665f5a98a2..fd37fd17b2 100644 --- a/UnitTests/Views/ScrollSliderTests.cs +++ b/UnitTests/Views/ScrollSliderTests.cs @@ -17,7 +17,7 @@ public class ScrollSliderTests public void Test_Position_Location_Consistency (Orientation orientation, int scrollLength, int size, int location, int expectedPosition) { // Arrange - Scroll host = new () + Scroll scroll = new () { Orientation = orientation, Width = orientation == Orientation.Vertical ? 1 : scrollLength, @@ -25,18 +25,18 @@ public void Test_Position_Location_Consistency (Orientation orientation, int scr Size = size }; - host.BeginInit (); - host.EndInit (); + scroll.BeginInit (); + scroll.EndInit (); // Act - host.Position = host._slider.GetPositionFromSliderLocation (location); - (int calculatedLocation, int calculatedDimension) = host._slider.GetSliderLocationDimensionFromPosition (); - int calculatedPosition = host._slider.GetPositionFromSliderLocation (calculatedLocation); + scroll.Position = scroll._slider.GetPositionFromSliderLocation (location); + (int calculatedLocation, int calculatedDimension) = scroll._slider.GetSliderLocationDimensionFromPosition (); + int calculatedPosition = scroll._slider.GetPositionFromSliderLocation (calculatedLocation); // Assert AssertLocation (scrollLength, location, calculatedLocation, calculatedDimension); - Assert.Equal (host.Position, expectedPosition); + Assert.Equal (scroll.Position, expectedPosition); Assert.Equal (calculatedPosition, expectedPosition); } @@ -47,7 +47,7 @@ public void Test_Position_Location_Consistency_Random (Orientation orientation, { var random = new Random (); - Scroll host = new () + Scroll scroll = new () { Orientation = orientation, Width = orientation == Orientation.Vertical ? 1 : scrollLength, @@ -55,8 +55,8 @@ public void Test_Position_Location_Consistency_Random (Orientation orientation, Size = size }; - host.BeginInit (); - host.EndInit (); + scroll.BeginInit (); + scroll.EndInit (); // Number of random tests to run for (var i = 0; i < testCount; i++) @@ -65,21 +65,21 @@ public void Test_Position_Location_Consistency_Random (Orientation orientation, int randomScrollLength = random.Next (0, 60); // Random content size length int randomLocation = random.Next (0, randomScrollLength); // Random location - host.Width = host.Orientation == Orientation.Vertical ? 1 : randomScrollLength; - host.Height = host.Orientation == Orientation.Vertical ? randomScrollLength : 1; + scroll.Width = scroll.Orientation == Orientation.Vertical ? 1 : randomScrollLength; + scroll.Height = scroll.Orientation == Orientation.Vertical ? randomScrollLength : 1; // Slider may have changed content size - host.LayoutSubviews (); + scroll.LayoutSubviews (); // Act - host.Position = host._slider.GetPositionFromSliderLocation (randomLocation); - (int calculatedLocation, int calculatedDimension) = host._slider.GetSliderLocationDimensionFromPosition (); - int calculatedPosition = host._slider.GetPositionFromSliderLocation (calculatedLocation); + scroll.Position = scroll._slider.GetPositionFromSliderLocation (randomLocation); + (int calculatedLocation, int calculatedDimension) = scroll._slider.GetSliderLocationDimensionFromPosition (); + int calculatedPosition = scroll._slider.GetPositionFromSliderLocation (calculatedLocation); // Assert AssertLocation (randomScrollLength, randomLocation, calculatedLocation, calculatedDimension); - Assert.Equal (host.Position, calculatedPosition); + Assert.Equal (scroll.Position, calculatedPosition); } } From e48914636ac0cebd9b43be45796013f4f0a1faa4 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 2 Sep 2024 22:12:00 +0100 Subject: [PATCH 044/128] Replaces VariationMode with NavigationDirection enum. --- Terminal.Gui/Views/Scroll/ScrollBar.cs | 2 +- Terminal.Gui/Views/Scroll/ScrollButton.cs | 27 +++++++++-------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index 5ebfa15ab6..d98bd19326 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -19,7 +19,7 @@ public ScrollBar () { _scroll = new (); _decrease = new (); - _increase = new () { VariationMode = VariationMode.Increase }; + _increase = new () { NavigationDirection = NavigationDirection.Forward }; Add (_scroll, _decrease, _increase); CanFocus = false; diff --git a/Terminal.Gui/Views/Scroll/ScrollButton.cs b/Terminal.Gui/Views/Scroll/ScrollButton.cs index d9064388c8..038edaa502 100644 --- a/Terminal.Gui/Views/Scroll/ScrollButton.cs +++ b/Terminal.Gui/Views/Scroll/ScrollButton.cs @@ -2,12 +2,6 @@ namespace Terminal.Gui; -internal enum VariationMode -{ - Decrease, - Increase -} - internal class ScrollButton : View { public ScrollButton () @@ -15,6 +9,7 @@ public ScrollButton () TextAlignment = Alignment.Center; VerticalTextAlignment = Alignment.Center; Id = "scrollButton"; + NavigationDirection = NavigationDirection.Backward; //Width = Dim.Auto (DimAutoStyle.Content, 1); //Height = Dim.Auto (DimAutoStyle.Content, 1); @@ -33,14 +28,14 @@ public void AdjustButton () Width = SupView.Orientation == Orientation.Vertical ? Dim.Fill () : 1; Height = SupView.Orientation == Orientation.Vertical ? 1 : Dim.Fill (); - switch (VariationMode) + switch (NavigationDirection) { - case VariationMode.Decrease: + case NavigationDirection.Backward: X = 0; Y = 0; break; - case VariationMode.Increase: + case NavigationDirection.Forward: X = SupView.Orientation == Orientation.Vertical ? 0 : Pos.AnchorEnd (1); Y = SupView.Orientation == Orientation.Vertical ? Pos.AnchorEnd (1) : 0; @@ -67,7 +62,7 @@ public override Attribute GetNormalColor () return base.GetNormalColor (); } - public VariationMode VariationMode { get; init; } + public NavigationDirection NavigationDirection { get; init; } /// protected internal override bool? OnMouseEnter (MouseEvent mouseEvent) @@ -91,13 +86,13 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) { - switch (VariationMode) + switch (NavigationDirection) { - case VariationMode.Decrease: + case NavigationDirection.Backward: SupView.Position--; return true; - case VariationMode.Increase: + case NavigationDirection.Forward: SupView.Position++; return true; @@ -123,13 +118,13 @@ protected internal override bool OnMouseLeave (MouseEvent mouseEvent) private void SetButtonText () { - switch (VariationMode) + switch (NavigationDirection) { - case VariationMode.Decrease: + case NavigationDirection.Backward: Text = SupView.Orientation == Orientation.Vertical ? Glyphs.UpArrow.ToString () : Glyphs.LeftArrow.ToString (); break; - case VariationMode.Increase: + case NavigationDirection.Forward: Text = SupView.Orientation == Orientation.Vertical ? Glyphs.DownArrow.ToString () : Glyphs.RightArrow.ToString (); break; From 27aa591aef1e0a73fb938ed35528086ecc1fbe54 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 2 Sep 2024 22:15:13 +0100 Subject: [PATCH 045/128] Remove Virtual. --- Terminal.Gui/Views/Scroll/Scroll.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 28d2037fed..598982b535 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -174,7 +174,7 @@ protected virtual CancelEventArgs OnPositionChanging (int currentPos, int n return args; } - /// Virtual method called when has changed. Raises . + /// Called when has changed. Raises . protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (in size)); } internal void AdjustScroll () From 734383294f2164999b2d592c4ba75c95d608609d Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 2 Sep 2024 22:47:13 +0100 Subject: [PATCH 046/128] Replace with SuperViewAsScrollBar. --- Terminal.Gui/Views/Scroll/ScrollButton.cs | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollButton.cs b/Terminal.Gui/Views/Scroll/ScrollButton.cs index 038edaa502..f7a877bd01 100644 --- a/Terminal.Gui/Views/Scroll/ScrollButton.cs +++ b/Terminal.Gui/Views/Scroll/ScrollButton.cs @@ -25,8 +25,8 @@ public void AdjustButton () return; } - Width = SupView.Orientation == Orientation.Vertical ? Dim.Fill () : 1; - Height = SupView.Orientation == Orientation.Vertical ? 1 : Dim.Fill (); + Width = SuperViewAsScrollBar.Orientation == Orientation.Vertical ? Dim.Fill () : 1; + Height = SuperViewAsScrollBar.Orientation == Orientation.Vertical ? 1 : Dim.Fill (); switch (NavigationDirection) { @@ -36,8 +36,8 @@ public void AdjustButton () break; case NavigationDirection.Forward: - X = SupView.Orientation == Orientation.Vertical ? 0 : Pos.AnchorEnd (1); - Y = SupView.Orientation == Orientation.Vertical ? Pos.AnchorEnd (1) : 0; + X = SuperViewAsScrollBar.Orientation == Orientation.Vertical ? 0 : Pos.AnchorEnd (1); + Y = SuperViewAsScrollBar.Orientation == Orientation.Vertical ? Pos.AnchorEnd (1) : 0; break; default: @@ -52,11 +52,11 @@ public override Attribute GetNormalColor () { if (_savedColorScheme is null) { - ColorScheme = new () { Normal = new (SupView.ColorScheme.HotNormal.Foreground, SupView.ColorScheme.HotNormal.Background) }; + ColorScheme = new () { Normal = new (SuperViewAsScrollBar.ColorScheme.HotNormal.Foreground, SuperViewAsScrollBar.ColorScheme.HotNormal.Background) }; } else { - ColorScheme = new () { Normal = new (SupView.ColorScheme.Normal.Background, SupView.ColorScheme.Normal.Foreground) }; + ColorScheme = new () { Normal = new (SuperViewAsScrollBar.ColorScheme.Normal.Background, SuperViewAsScrollBar.ColorScheme.Normal.Foreground) }; } return base.GetNormalColor (); @@ -67,7 +67,7 @@ public override Attribute GetNormalColor () /// protected internal override bool? OnMouseEnter (MouseEvent mouseEvent) { - _savedColorScheme ??= SupView.ColorScheme; + _savedColorScheme ??= SuperViewAsScrollBar.ColorScheme; ColorScheme = new () { @@ -89,11 +89,11 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) switch (NavigationDirection) { case NavigationDirection.Backward: - SupView.Position--; + SuperViewAsScrollBar.Position--; return true; case NavigationDirection.Forward: - SupView.Position++; + SuperViewAsScrollBar.Position++; return true; default: @@ -121,11 +121,11 @@ private void SetButtonText () switch (NavigationDirection) { case NavigationDirection.Backward: - Text = SupView.Orientation == Orientation.Vertical ? Glyphs.UpArrow.ToString () : Glyphs.LeftArrow.ToString (); + Text = SuperViewAsScrollBar.Orientation == Orientation.Vertical ? Glyphs.UpArrow.ToString () : Glyphs.LeftArrow.ToString (); break; case NavigationDirection.Forward: - Text = SupView.Orientation == Orientation.Vertical ? Glyphs.DownArrow.ToString () : Glyphs.RightArrow.ToString (); + Text = SuperViewAsScrollBar.Orientation == Orientation.Vertical ? Glyphs.DownArrow.ToString () : Glyphs.RightArrow.ToString (); break; default: @@ -133,5 +133,5 @@ private void SetButtonText () } } - private ScrollBar SupView => (SuperView as ScrollBar)!; + private ScrollBar SuperViewAsScrollBar => (SuperView as ScrollBar)!; } From b16b4637a8656920133c49decf90d5ce878d3bbc Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 2 Sep 2024 22:58:21 +0100 Subject: [PATCH 047/128] Doc changes addressed by @tig. --- Terminal.Gui/Views/Scroll/Scroll.cs | 8 +++++++- Terminal.Gui/Views/Scroll/ScrollBar.cs | 7 +++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 598982b535..077edb838f 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -5,8 +5,14 @@ namespace Terminal.Gui; /// -/// Provides a proportional control for scrolling through content. Used within a . +/// Indicates the position and size of scrollable content. The indicator can be dragged with the mouse. Can be +/// oriented either vertically or horizontally. Used within a . /// +/// +/// +/// By default, this view cannot be focused and does not support keyboard. +/// +/// public class Scroll : View { /// diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index d98bd19326..e51052cb4f 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -4,13 +4,12 @@ namespace Terminal.Gui; -/// ScrollBars are views that display a 1-character scrollbar, either horizontal or vertical +/// A proportional scroll bar that can be oriented either horizontally or vertically. /// /// -/// The scrollbar is drawn to be a representation of the Size, assuming that the scroll position is set at -/// Position. +/// indicates the current location between zero and . /// -/// If the region to display the scrollbar is larger than three characters, arrow indicators are drawn. +/// If the scrollbar is larger than three cells, arrow indicators are drawn. /// public class ScrollBar : View { From 11fc8930bb9885f54a0347704456b036138b20ff Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 3 Sep 2024 00:06:04 +0100 Subject: [PATCH 048/128] Remove commented code. --- Terminal.Gui/Views/Scroll/ScrollButton.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollButton.cs b/Terminal.Gui/Views/Scroll/ScrollButton.cs index f7a877bd01..436a6252d2 100644 --- a/Terminal.Gui/Views/Scroll/ScrollButton.cs +++ b/Terminal.Gui/Views/Scroll/ScrollButton.cs @@ -10,9 +10,6 @@ public ScrollButton () VerticalTextAlignment = Alignment.Center; Id = "scrollButton"; NavigationDirection = NavigationDirection.Backward; - - //Width = Dim.Auto (DimAutoStyle.Content, 1); - //Height = Dim.Auto (DimAutoStyle.Content, 1); WantContinuousButtonPressed = true; } From f0d2bbeda74a1acbce6b684bfe488cffb3bf15bd Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 3 Sep 2024 00:17:54 +0100 Subject: [PATCH 049/128] Rename to SuperViewAsScroll. --- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 70 +++++++++++------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 0e640354be..3f8d9713a9 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -23,13 +23,13 @@ public void AdjustSlider () } (int Location, int Dimension) sliderLocationAndDimension = GetSliderLocationDimensionFromPosition (); - X = SupView.Orientation == Orientation.Vertical ? 0 : sliderLocationAndDimension.Location; - Y = SupView.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Location : 0; + X = SuperViewAsScroll.Orientation == Orientation.Vertical ? 0 : sliderLocationAndDimension.Location; + Y = SuperViewAsScroll.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Location : 0; SetContentSize ( new ( - SupView.Orientation == Orientation.Vertical ? SupView.GetContentSize ().Width : sliderLocationAndDimension.Dimension, - SupView.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Dimension : SupView.GetContentSize ().Height + SuperViewAsScroll.Orientation == Orientation.Vertical ? SuperViewAsScroll.GetContentSize ().Width : sliderLocationAndDimension.Dimension, + SuperViewAsScroll.Orientation == Orientation.Vertical ? sliderLocationAndDimension.Dimension : SuperViewAsScroll.GetContentSize ().Height )); SetSliderText (); } @@ -39,11 +39,11 @@ public override Attribute GetNormalColor () { if (_savedColorScheme is null) { - ColorScheme = new () { Normal = new (SupView.ColorScheme.HotNormal.Foreground, SupView.ColorScheme.HotNormal.Foreground) }; + ColorScheme = new () { Normal = new (SuperViewAsScroll.ColorScheme.HotNormal.Foreground, SuperViewAsScroll.ColorScheme.HotNormal.Foreground) }; } else { - ColorScheme = new () { Normal = new (SupView.ColorScheme.Normal.Foreground, SupView.ColorScheme.Normal.Foreground) }; + ColorScheme = new () { Normal = new (SuperViewAsScroll.ColorScheme.Normal.Foreground, SuperViewAsScroll.ColorScheme.Normal.Foreground) }; } return base.GetNormalColor (); @@ -52,7 +52,7 @@ public override Attribute GetNormalColor () /// protected internal override bool? OnMouseEnter (MouseEvent mouseEvent) { - _savedColorScheme ??= SupView.ColorScheme; + _savedColorScheme ??= SuperViewAsScroll.ColorScheme; ColorScheme = new () { @@ -69,9 +69,9 @@ public override Attribute GetNormalColor () /// protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { - int location = SupView.Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; + int location = SuperViewAsScroll.Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; int offset = _lastLocation > -1 ? location - _lastLocation : 0; - int barSize = SupView.Orientation == Orientation.Vertical ? SupView.GetContentSize ().Height : SupView.GetContentSize ().Width; + int barSize = SuperViewAsScroll.Orientation == Orientation.Vertical ? SuperViewAsScroll.GetContentSize ().Height : SuperViewAsScroll.GetContentSize ().Width; if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && _lastLocation == -1) { @@ -83,19 +83,19 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) } else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - if (SupView.Orientation == Orientation.Vertical) + if (SuperViewAsScroll.Orientation == Orientation.Vertical) { Y = Frame.Y + offset < 0 ? 0 : Frame.Y + offset + Frame.Height > barSize ? Math.Max (barSize - Frame.Height, 0) : Frame.Y + offset; - SupView.Position = GetPositionFromSliderLocation (Frame.Y); + SuperViewAsScroll.Position = GetPositionFromSliderLocation (Frame.Y); } else { X = Frame.X + offset < 0 ? 0 : Frame.X + offset + Frame.Width > barSize ? Math.Max (barSize - Frame.Width, 0) : Frame.X + offset; - SupView.Position = GetPositionFromSliderLocation (Frame.X); + SuperViewAsScroll.Position = GetPositionFromSliderLocation (Frame.X); } } else if (mouseEvent.Flags == MouseFlags.Button1Released) @@ -107,15 +107,15 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) Application.UngrabMouse (); } } - else if ((mouseEvent.Flags == MouseFlags.WheeledDown && SupView.Orientation == Orientation.Vertical) - || (mouseEvent.Flags == MouseFlags.WheeledRight && SupView.Orientation == Orientation.Horizontal)) + else if ((mouseEvent.Flags == MouseFlags.WheeledDown && SuperViewAsScroll.Orientation == Orientation.Vertical) + || (mouseEvent.Flags == MouseFlags.WheeledRight && SuperViewAsScroll.Orientation == Orientation.Horizontal)) { - SupView.Position = Math.Min (SupView.Position + 1, SupView.Size - barSize); + SuperViewAsScroll.Position = Math.Min (SuperViewAsScroll.Position + 1, SuperViewAsScroll.Size - barSize); } - else if ((mouseEvent.Flags == MouseFlags.WheeledUp && SupView.Orientation == Orientation.Vertical) - || (mouseEvent.Flags == MouseFlags.WheeledLeft && SupView.Orientation == Orientation.Horizontal)) + else if ((mouseEvent.Flags == MouseFlags.WheeledUp && SuperViewAsScroll.Orientation == Orientation.Vertical) + || (mouseEvent.Flags == MouseFlags.WheeledLeft && SuperViewAsScroll.Orientation == Orientation.Horizontal)) { - SupView.Position = Math.Max (SupView.Position - 1, 0); + SuperViewAsScroll.Position = Math.Max (SuperViewAsScroll.Position - 1, 0); } else if (mouseEvent.Flags != MouseFlags.ReportMousePosition) { @@ -139,48 +139,48 @@ protected internal override bool OnMouseLeave (MouseEvent mouseEvent) internal int GetPositionFromSliderLocation (int location) { - if (SupView.GetContentSize ().Height == 0 || SupView.GetContentSize ().Width == 0) + if (SuperViewAsScroll.GetContentSize ().Height == 0 || SuperViewAsScroll.GetContentSize ().Width == 0) { return 0; } - int scrollSize = SupView.Orientation == Orientation.Vertical ? SupView.GetContentSize ().Height : SupView.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 - if ((SupView.Orientation == Orientation.Vertical && location + Frame.Height >= scrollSize) - || (SupView.Orientation == Orientation.Horizontal && location + Frame.Width >= scrollSize)) + if ((SuperViewAsScroll.Orientation == Orientation.Vertical && location + Frame.Height >= scrollSize) + || (SuperViewAsScroll.Orientation == Orientation.Horizontal && location + Frame.Width >= scrollSize)) { - return SupView.Size - scrollSize; + return SuperViewAsScroll.Size - scrollSize; } - return (int)Math.Min (Math.Round ((double)(location * SupView.Size + location) / scrollSize), SupView.Size - scrollSize); + return (int)Math.Min (Math.Round ((double)(location * SuperViewAsScroll.Size + location) / scrollSize), SuperViewAsScroll.Size - scrollSize); } internal (int Location, int Dimension) GetSliderLocationDimensionFromPosition () { - if (SupView.GetContentSize ().Height == 0 || SupView.GetContentSize ().Width == 0) + if (SuperViewAsScroll.GetContentSize ().Height == 0 || SuperViewAsScroll.GetContentSize ().Width == 0) { return new (0, 0); } - int scrollSize = SupView.Orientation == Orientation.Vertical ? SupView.GetContentSize ().Height : SupView.GetContentSize ().Width; + int scrollSize = SuperViewAsScroll.Orientation == Orientation.Vertical ? SuperViewAsScroll.GetContentSize ().Height : SuperViewAsScroll.GetContentSize ().Width; int location; int dimension; - if (SupView.Size > 0) + if (SuperViewAsScroll.Size > 0) { - dimension = (int)Math.Min (Math.Max (Math.Ceiling ((double)scrollSize * scrollSize / SupView.Size), 1), scrollSize); + dimension = (int)Math.Min (Math.Max (Math.Ceiling ((double)scrollSize * scrollSize / SuperViewAsScroll.Size), 1), scrollSize); // Ensure the Position is valid - if (SupView.Position > 0 && SupView.Position + scrollSize > SupView.Size) + if (SuperViewAsScroll.Position > 0 && SuperViewAsScroll.Position + scrollSize > SuperViewAsScroll.Size) { - SupView.Position = SupView.Size - scrollSize; + SuperViewAsScroll.Position = SuperViewAsScroll.Size - scrollSize; } - location = (int)Math.Min (Math.Round ((double)SupView.Position * scrollSize / (SupView.Size + 1)), scrollSize - dimension); + location = (int)Math.Min (Math.Round ((double)SuperViewAsScroll.Position * scrollSize / (SuperViewAsScroll.Size + 1)), scrollSize - dimension); - if (SupView.Position == SupView.Size - scrollSize && location + dimension < scrollSize) + if (SuperViewAsScroll.Position == SuperViewAsScroll.Size - scrollSize && location + dimension < scrollSize) { location = scrollSize - dimension; } @@ -200,14 +200,14 @@ internal int GetPositionFromSliderLocation (int location) private void SetSliderText () { - TextDirection = SupView.Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; + TextDirection = SuperViewAsScroll.Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; // QUESTION: Should these Glyphs be configurable via CM? Text = string.Concat ( Enumerable.Repeat ( Glyphs.ContinuousMeterSegment.ToString (), - SupView.GetContentSize ().Width * SupView.GetContentSize ().Height)); + SuperViewAsScroll.GetContentSize ().Width * SuperViewAsScroll.GetContentSize ().Height)); } - private Scroll SupView => (SuperView as Scroll)!; + private Scroll SuperViewAsScroll => (SuperView as Scroll)!; } From 1804d08ae53004dda82cefe443aeb037f374de80 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 3 Sep 2024 14:06:17 +0100 Subject: [PATCH 050/128] Add private BarSize method. --- Terminal.Gui/Views/Scroll/Scroll.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 077edb838f..12ba1ade0f 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -74,7 +74,7 @@ public int Position SetRelativeLayout (SupView.Frame.Size); } - int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; + int barSize = BarSize; if (value + barSize > Size) { @@ -126,7 +126,7 @@ public int Size protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; - int barSize = Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; + int barSize = BarSize; (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) @@ -207,6 +207,8 @@ internal override void OnLayoutComplete (LayoutEventArgs args) internal ScrollBar? SupView => SuperView as ScrollBar; + private int BarSize => Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; + private void SetScrollText () { TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; From c43de60c08f34d042f20dfacd73dff3be82b3dfc Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 3 Sep 2024 19:34:19 +0100 Subject: [PATCH 051/128] BarSize should use Viewport. --- Terminal.Gui/Views/Scroll/Scroll.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 12ba1ade0f..a5839feeae 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -207,7 +207,7 @@ internal override void OnLayoutComplete (LayoutEventArgs args) internal ScrollBar? SupView => SuperView as ScrollBar; - private int BarSize => Orientation == Orientation.Vertical ? GetContentSize ().Height : GetContentSize ().Width; + private int BarSize => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width; private void SetScrollText () { From 70b3be289b26051f5cbafa224286812da976f510 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 3 Sep 2024 19:43:28 +0100 Subject: [PATCH 052/128] The barSize should use the Scroll Viewport. --- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 3f8d9713a9..dce3e788c0 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -71,7 +71,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { int location = SuperViewAsScroll.Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; int offset = _lastLocation > -1 ? location - _lastLocation : 0; - int barSize = SuperViewAsScroll.Orientation == Orientation.Vertical ? SuperViewAsScroll.GetContentSize ().Height : SuperViewAsScroll.GetContentSize ().Width; + int barSize = SuperViewAsScroll.Orientation == Orientation.Vertical ? SuperViewAsScroll.Viewport.Height : SuperViewAsScroll.Viewport.Width; if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && _lastLocation == -1) { From 6c972c67c34e7adcece2cc45f70b21594882e7cc Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 3 Sep 2024 23:32:30 +0100 Subject: [PATCH 053/128] Add AutoHideScrollBar and ShowScrollIndicator properties. --- Terminal.Gui/Views/Scroll/ScrollBar.cs | 88 ++++++++++++ UICatalog/Scenarios/ScrollBarDemo.cs | 10 +- UnitTests/Views/ScrollBarTests.cs | 178 ++++++++++++++++++------- 3 files changed, 227 insertions(+), 49 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index e51052cb4f..52e51699e7 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -35,6 +35,23 @@ public ScrollBar () private readonly ScrollButton _decrease; private readonly ScrollButton _increase; + private bool _autoHideScrollBar = true; + private bool _showScrollIndicator = true; + + /// If true the vertical/horizontal scroll bars won't be shown if it's not needed. + public bool AutoHideScrollBar + { + get => _autoHideScrollBar; + set + { + if (_autoHideScrollBar != value) + { + _autoHideScrollBar = value; + AdjustAll (); + } + } + } + /// Defines if a scrollbar is vertical or horizontal. public Orientation Orientation { @@ -67,6 +84,39 @@ public int Position /// public event EventHandler>? PositionChanging; + /// Gets or sets the visibility for the vertical or horizontal scroll indicator. + /// true if show vertical or horizontal scroll indicator; otherwise, false. + public bool ShowScrollIndicator + { + get => Visible; + set + { + if (value == _showScrollIndicator) + { + return; + } + + _showScrollIndicator = value; + + if (IsInitialized) + { + SetNeedsLayout (); + + if (value) + { + Visible = true; + } + else + { + Visible = false; + Position = 0; + } + + AdjustAll (); + } + } + } + /// /// Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through. /// @@ -95,11 +145,49 @@ internal override void OnLayoutComplete (LayoutEventArgs args) private void AdjustAll () { + CheckScrollBarVisibility (); _scroll.AdjustScroll (); _decrease.AdjustButton (); _increase.AdjustButton (); } + private bool CheckScrollBarVisibility () + { + if (!AutoHideScrollBar) + { + if (Visible != _showScrollIndicator) + { + Visible = _showScrollIndicator; + SetNeedsDisplay (); + } + + return _showScrollIndicator; + } + + int barSize = Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width; + + if (barSize == 0 || barSize >= Size) + { + if (ShowScrollIndicator) + { + Visible = false; + SetNeedsDisplay (); + + return false; + } + } + else + { + if (!Visible) + { + Visible = true; + SetNeedsDisplay (); + } + } + + return true; + } + private void Resize (Orientation orientation) { switch (orientation) diff --git a/UICatalog/Scenarios/ScrollBarDemo.cs b/UICatalog/Scenarios/ScrollBarDemo.cs index 284c7f40d6..e0ff82f86f 100644 --- a/UICatalog/Scenarios/ScrollBarDemo.cs +++ b/UICatalog/Scenarios/ScrollBarDemo.cs @@ -182,9 +182,17 @@ public override void Main () } }; + var ckbAutoHideScrollBar = new CheckBox { Y = Pos.Bottom (scrollPosition), Text = "AutoHideScrollBar" }; + ckbAutoHideScrollBar.CheckedStateChanging += (s, e) => scrollBar.AutoHideScrollBar = e.NewValue == CheckState.Checked; + view.Add (ckbAutoHideScrollBar); + + var ckbShowScrollIndicator = new CheckBox { X = Pos.Right (ckbAutoHideScrollBar) + 1, Y = Pos.Bottom (scrollPosition), Text = "ShowScrollIndicator" }; + ckbShowScrollIndicator.CheckedStateChanging += (s, e) => scrollBar.ShowScrollIndicator = e.NewValue == CheckState.Checked; + view.Add (ckbShowScrollIndicator); + var lblSizeChanged = new Label { - Y = Pos.Bottom (lblPosition) + 1 + Y = Pos.Bottom (ckbShowScrollIndicator) + 1 }; view.Add (lblSizeChanged); diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs index 12129eceb1..10497ffa14 100644 --- a/UnitTests/Views/ScrollBarTests.cs +++ b/UnitTests/Views/ScrollBarTests.cs @@ -7,6 +7,42 @@ public class ScrollBarTests public ScrollBarTests (ITestOutputHelper output) { _output = output; } private readonly ITestOutputHelper _output; + [Fact] + [AutoInitShutdown] + public void AutoHideScrollBar_CheckScrollBarVisibility () + { + var scrollBar = new ScrollBar { Width = 2, Height = Dim.Fill (), Size = 30 }; + View scrollBarSuperView = ScrollBarSuperView (); + scrollBarSuperView.Add (scrollBar); + Application.Begin ((scrollBarSuperView.SuperView as Toplevel)!); + + Assert.Equal (Orientation.Vertical, scrollBar.Orientation); + 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; + Assert.False (scrollBar.ShowScrollIndicator); + Assert.False (scrollBar.Visible); + + scrollBar.Size = 30; + Assert.True (scrollBar.ShowScrollIndicator); + Assert.True (scrollBar.Visible); + + scrollBar.AutoHideScrollBar = false; + Assert.True (scrollBar.ShowScrollIndicator); + Assert.True (scrollBar.Visible); + + scrollBar.Size = 10; + Assert.True (scrollBar.ShowScrollIndicator); + Assert.True (scrollBar.Visible); + + scrollBarSuperView.SuperView!.Dispose (); + } + [Theory] [AutoInitShutdown] [InlineData ( @@ -189,6 +225,10 @@ public void Constructor_Defaults () Assert.Equal (Orientation.Vertical, scrollBar.Orientation); Assert.Equal (0, scrollBar.Size); Assert.Equal (0, scrollBar.Position); + Assert.Equal ("Auto(Content,Absolute(1),)", scrollBar.Width!.ToString ()); + Assert.Equal ("Auto(Content,Absolute(1),)", scrollBar.Height!.ToString ()); + Assert.True (scrollBar.ShowScrollIndicator); + Assert.True (scrollBar.AutoHideScrollBar); } [Theory] @@ -707,6 +747,65 @@ string expectedOut Assert.Null (Application.MouseGrabView); } + [Theory] + [AutoInitShutdown] + [InlineData (Orientation.Vertical)] + [InlineData (Orientation.Horizontal)] + public void Mouse_Pressed_On_ScrollButton_Changes_Position (Orientation orientation) + { + var scrollBar = new ScrollBar + { + X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, + Orientation = orientation + }; + var top = new Toplevel (); + top.Add (scrollBar); + Application.Begin (top); + + var scroll = (Scroll)scrollBar.Subviews.FirstOrDefault (x => x is Scroll); + Rectangle scrollSliderFrame = scroll!.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame; + Assert.Equal (scrollSliderFrame, orientation == Orientation.Vertical ? new (0, 0, 1, 4) : new (0, 0, 4, 1)); + Assert.Equal (0, scrollBar.Position); + + // ScrollButton increase + for (var i = 0; i < 13; i++) + { + Application.OnMouseEvent ( + new () + { + Position = orientation == Orientation.Vertical ? new (10, 19) : new (19, 10), Flags = MouseFlags.Button1Pressed + }); + + if (i < 12) + { + Assert.Equal (i + 1, scrollBar.Position); + } + else + { + Assert.Equal (i, scrollBar.Position); + + Assert.Equal ( + orientation == Orientation.Vertical ? new (0, 4) : new (4, 0), + scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); + } + } + + for (var i = 12; i > -1; i--) + { + Application.OnMouseEvent (new () { Position = new (10, 10), Flags = MouseFlags.Button1Pressed }); + + if (i > 0) + { + Assert.Equal (i - 1, scrollBar.Position); + } + else + { + Assert.Equal (0, scrollBar.Position); + Assert.Equal (new (0, 0), scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); + } + } + } + [Theory] [AutoInitShutdown] [InlineData (Orientation.Vertical)] @@ -877,6 +976,30 @@ void Reset () } } + [Fact] + [AutoInitShutdown] + public void ShowScrollIndicator_CheckScrollBarVisibility () + { + var scrollBar = new ScrollBar { Width = 2, Height = Dim.Fill (), Size = 30 }; + View scrollBarSuperView = ScrollBarSuperView (); + scrollBarSuperView.Add (scrollBar); + Application.Begin ((scrollBarSuperView.SuperView as Toplevel)!); + + Assert.True (scrollBar.ShowScrollIndicator); + Assert.True (scrollBar.Visible); + + scrollBar.ShowScrollIndicator = false; + Assert.True (scrollBar.AutoHideScrollBar); + Assert.True (scrollBar.ShowScrollIndicator); + Assert.True (scrollBar.Visible); + + scrollBar.AutoHideScrollBar = false; + Assert.False (scrollBar.ShowScrollIndicator); + Assert.False (scrollBar.Visible); + + scrollBarSuperView.SuperView!.Dispose (); + } + [Fact] public void SizeChanged_Event () { @@ -967,58 +1090,17 @@ public void Vertical_Horizontal_Draws_Correctly (int sizeWidth, int sizeHeight, _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); } - [Theory] - [AutoInitShutdown] - [InlineData (Orientation.Vertical)] - [InlineData (Orientation.Horizontal)] - public void Mouse_Pressed_On_ScrollButton_Changes_Position (Orientation orientation) + private View ScrollBarSuperView () { - var scrollBar = new ScrollBar + var view = new View { - X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, - Orientation = orientation + Width = Dim.Fill (), + Height = Dim.Fill () }; - var top = new Toplevel (); - top.Add (scrollBar); - Application.Begin (top); - - var scroll = (Scroll)scrollBar.Subviews.FirstOrDefault (x => x is Scroll); - Rectangle scrollSliderFrame = scroll!.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame; - Assert.Equal (scrollSliderFrame, orientation == Orientation.Vertical ? new (0, 0, 1, 4) : new (0, 0, 4, 1)); - Assert.Equal (0, scrollBar.Position); - - // ScrollButton increase - for (int i = 0; i < 13; i++) - { - Application.OnMouseEvent (new () { Position = orientation == Orientation.Vertical ? new (10, 19) : new (19, 10), Flags = MouseFlags.Button1Pressed }); - - if (i < 12) - { - Assert.Equal (i + 1, scrollBar.Position); - } - else - { - Assert.Equal (i, scrollBar.Position); - Assert.Equal ( - orientation == Orientation.Vertical ? new (0, 4) : new (4, 0), - scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); - } - } - - for (int i = 12; i > -1; i--) - { - Application.OnMouseEvent (new () { Position = new (10, 10), Flags = MouseFlags.Button1Pressed }); + var top = new Toplevel (); + top.Add (view); - if (i > 0) - { - Assert.Equal (i - 1, scrollBar.Position); - } - else - { - Assert.Equal (0, scrollBar.Position); - Assert.Equal (new (0, 0), scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); - } - } + return view; } } From e2a2aa12a6541ac357512f14a1c78367752281e1 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 3 Sep 2024 17:29:58 -0600 Subject: [PATCH 054/128] Upgraded CharMap to use new ScrollBar --- UICatalog/Scenarios/CharacterMap.cs | 98 +++++++++++------------------ UICatalog/Scenarios/TableEditor.cs | 4 +- 2 files changed, 39 insertions(+), 63 deletions(-) diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index fba3354db5..8b68772c1f 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -323,7 +323,7 @@ public CharMap () CanFocus = true; CursorVisibility = CursorVisibility.Default; - SetContentSize (new (RowWidth, (MaxCodePoint / 16 + 2) * _rowHeight)); + SetContentSize (new (RowWidth, (_maxCodePoint / 16 + 2) * _rowHeight)); AddCommand ( Command.ScrollUp, @@ -344,7 +344,7 @@ public CharMap () Command.ScrollDown, () => { - if (SelectedCodePoint <= MaxCodePoint - 16) + if (SelectedCodePoint <= _maxCodePoint - 16) { SelectedCodePoint += 16; } @@ -380,7 +380,7 @@ public CharMap () Command.ScrollRight, () => { - if (SelectedCodePoint < MaxCodePoint) + if (SelectedCodePoint < _maxCodePoint) { SelectedCodePoint++; } @@ -411,7 +411,7 @@ public CharMap () () => { int page = (Viewport.Height - 1 / _rowHeight) * 16; - SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint); + SelectedCodePoint += Math.Min (page, _maxCodePoint - SelectedCodePoint); Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight }; return true; @@ -432,7 +432,7 @@ public CharMap () Command.BottomEnd, () => { - SelectedCodePoint = MaxCodePoint; + SelectedCodePoint = _maxCodePoint; Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight }; return true; @@ -462,66 +462,42 @@ public CharMap () MouseClick += Handle_MouseClick; MouseEvent += Handle_MouseEvent; - // Prototype scrollbars - Padding.Thickness = new (0, 0, 1, 1); - - var up = new Button + // Add scrollbars + Padding.Thickness = new (0, 0, 1, 0); + ScrollBar hScrollBar = new () { - X = Pos.AnchorEnd (1), - Y = 0, - Height = 1, - Width = 1, - NoPadding = true, - NoDecorations = true, - Title = CM.Glyphs.UpArrow.ToString (), - WantContinuousButtonPressed = true, - CanFocus = false + X = 0, + Y = Pos.AnchorEnd (), + Width = Dim.Fill (1), + Size = GetContentSize ().Width, + Orientation = Orientation.Horizontal }; - up.Accept += (sender, args) => { args.Handled = ScrollVertical (-1) == true; }; - var down = new Button - { - X = Pos.AnchorEnd (1), - Y = Pos.AnchorEnd (2), - Height = 1, - Width = 1, - NoPadding = true, - NoDecorations = true, - Title = CM.Glyphs.DownArrow.ToString (), - WantContinuousButtonPressed = true, - CanFocus = false - }; - down.Accept += (sender, args) => { ScrollVertical (1); }; + hScrollBar.VisibleChanged += (sender, args) => + { + Padding.Thickness = Padding.Thickness with { Bottom = hScrollBar.Visible ? 1 : 0 }; + }; - var left = new Button + ScrollBar vScrollBar = new () { - X = 0, - Y = Pos.AnchorEnd (1), - Height = 1, - Width = 1, - NoPadding = true, - NoDecorations = true, - Title = CM.Glyphs.LeftArrow.ToString (), - WantContinuousButtonPressed = true, - CanFocus = false + X = Pos.AnchorEnd (), + Y = 0, + Height = Dim.Fill (Dim.Func (() => hScrollBar.Visible ? 1 : 0)), + Orientation = Orientation.Vertical, + Size = GetContentSize ().Height }; - left.Accept += (sender, args) => { ScrollHorizontal (-1); }; + vScrollBar.PositionChanged += (sender, args) => { Viewport = Viewport with { Y = args.CurrentValue }; }; - var right = new Button - { - X = Pos.AnchorEnd (2), - Y = Pos.AnchorEnd (1), - Height = 1, - Width = 1, - NoPadding = true, - NoDecorations = true, - Title = CM.Glyphs.RightArrow.ToString (), - WantContinuousButtonPressed = true, - CanFocus = false - }; - right.Accept += (sender, args) => { ScrollHorizontal (1); }; + Padding.Add (vScrollBar, hScrollBar); + hScrollBar.PositionChanged += (sender, args) => { Viewport = Viewport with { X = args.CurrentValue }; }; - Padding.Add (up, down, left, right); + ViewportChanged += UpdateVertialScrollBar; + + void UpdateVertialScrollBar (object sender, DrawEventArgs e) + { + vScrollBar.Size = GetContentSize ().Height; + vScrollBar.Position = Viewport.Y; + } } private void Handle_MouseEvent (object sender, MouseEventEventArgs e) @@ -571,7 +547,7 @@ public Point Cursor set => throw new NotImplementedException (); } - public static int MaxCodePoint = UnicodeRange.Ranges.Max (r => r.End); + public static int _maxCodePoint = UnicodeRange.Ranges.Max (r => r.End); /// /// Specifies the starting offset for the character map. The default is 0x2500 which is the Box Drawing @@ -650,7 +626,7 @@ public int StartCodePoint } } - private static int RowLabelWidth => $"U+{MaxCodePoint:x5}".Length + 1; + private static int RowLabelWidth => $"U+{_maxCodePoint:x5}".Length + 1; private static int RowWidth => RowLabelWidth + COLUMN_WIDTH * 16; public event EventHandler Hover; @@ -698,7 +674,7 @@ public override void OnDrawContent (Rectangle viewport) int val = row * 16; - if (val > MaxCodePoint) + if (val > _maxCodePoint) { break; } @@ -865,7 +841,7 @@ private void Handle_MouseClick (object sender, MouseEventEventArgs args) int val = row * 16 + col; - if (val > MaxCodePoint) + if (val > _maxCodePoint) { return; } diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index 677d5fe355..32cfe05903 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -66,8 +66,8 @@ public class TableEditor : Scenario "Cuneiform Numbers and Punctuation" ), new ( - (uint)(CharMap.MaxCodePoint - 16), - (uint)CharMap.MaxCodePoint, + (uint)(CharMap._maxCodePoint - 16), + (uint)CharMap._maxCodePoint, "End" ), new (0x0020, 0x007F, "Basic Latin"), From cb1c793f43dd262e5d83774fb2f3808df78e551b Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 3 Sep 2024 17:38:52 -0600 Subject: [PATCH 055/128] Code cleanup and API doc improvements --- Terminal.Gui/Views/Scroll/ScrollBar.cs | 22 ++++++++++---------- UICatalog/Scenarios/CharacterMap.cs | 28 ++++++++++---------------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index 52e51699e7..0013f8878a 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -35,24 +35,24 @@ public ScrollBar () private readonly ScrollButton _decrease; private readonly ScrollButton _increase; - private bool _autoHideScrollBar = true; + private bool _autoHide = true; private bool _showScrollIndicator = true; - /// If true the vertical/horizontal scroll bars won't be shown if it's not needed. - public bool AutoHideScrollBar + /// Gets or sets whether will be set to if the dimension of the scroll bar is greater than or equal to . + public bool AutoHide { - get => _autoHideScrollBar; + get => _autoHide; set { - if (_autoHideScrollBar != value) + if (_autoHide != value) { - _autoHideScrollBar = value; + _autoHide = value; AdjustAll (); } } } - /// Defines if a scrollbar is vertical or horizontal. + /// Gets or sets if a scrollbar is vertical or horizontal. public Orientation Orientation { get => _scroll.Orientation; @@ -63,7 +63,7 @@ public Orientation Orientation } } - /// The position, relative to , to set the scrollbar at. + /// Gets or sets the position, relative to , to set the scrollbar at. /// The position. public int Position { @@ -145,15 +145,15 @@ internal override void OnLayoutComplete (LayoutEventArgs args) private void AdjustAll () { - CheckScrollBarVisibility (); + CheckVisibility (); _scroll.AdjustScroll (); _decrease.AdjustButton (); _increase.AdjustButton (); } - private bool CheckScrollBarVisibility () + private bool CheckVisibility () { - if (!AutoHideScrollBar) + if (!AutoHide) { if (Visible != _showScrollIndicator) { diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 8b68772c1f..b984d338f5 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -27,7 +27,6 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Controls")] [ScenarioCategory ("Layout")] [ScenarioCategory ("Scrolling")] - public class CharacterMap : Scenario { public Label _errorLabel; @@ -243,7 +242,6 @@ void JumpEditOnAccept (object sender, HandledEventArgs e) // Ensure the typed glyph is selected _charMap.SelectedCodePoint = (int)result; - // Cancel the event to prevent ENTER from being handled elsewhere e.Handled = true; } @@ -305,7 +303,6 @@ private MenuItem CreateMenuShowWidth () return item; } - } internal class CharMap : View @@ -464,6 +461,7 @@ public CharMap () // Add scrollbars Padding.Thickness = new (0, 0, 1, 0); + ScrollBar hScrollBar = new () { X = 0, @@ -473,10 +471,7 @@ public CharMap () Orientation = Orientation.Horizontal }; - hScrollBar.VisibleChanged += (sender, args) => - { - Padding.Thickness = Padding.Thickness with { Bottom = hScrollBar.Visible ? 1 : 0 }; - }; + hScrollBar.VisibleChanged += (sender, args) => { Padding.Thickness = Padding.Thickness with { Bottom = hScrollBar.Visible ? 1 : 0 }; }; ScrollBar vScrollBar = new () { @@ -491,14 +486,12 @@ public CharMap () Padding.Add (vScrollBar, hScrollBar); hScrollBar.PositionChanged += (sender, args) => { Viewport = Viewport with { X = args.CurrentValue }; }; - ViewportChanged += UpdateVertialScrollBar; - - void UpdateVertialScrollBar (object sender, DrawEventArgs e) - { - vScrollBar.Size = GetContentSize ().Height; - vScrollBar.Position = Viewport.Y; - } - } + ViewportChanged += (sender, args) => + { + vScrollBar.Size = GetContentSize ().Height; + vScrollBar.Position = Viewport.Y; + }; + }; private void Handle_MouseEvent (object sender, MouseEventEventArgs e) { @@ -861,6 +854,7 @@ private void Handle_MouseClick (object sender, MouseEventEventArgs args) if (me.Flags == MouseFlags.Button1Clicked) { SelectedCodePoint = val; + return; } @@ -976,7 +970,7 @@ private void ShowDetails () document.RootElement, new JsonSerializerOptions - { WriteIndented = true } + { WriteIndented = true } ); } @@ -1053,7 +1047,7 @@ private void ShowDetails () }; dlg.Add (label); - var json = new TextView () + var json = new TextView { X = 0, Y = Pos.Bottom (label), From 7983500dfbc9366b69b89aaef5c9f937af94da94 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 4 Sep 2024 12:06:59 +0100 Subject: [PATCH 056/128] Fix @tig branch errors. --- UICatalog/Scenarios/CharacterMap.cs | 2 +- UICatalog/Scenarios/ScrollBarDemo.cs | 2 +- UnitTests/Views/ScrollBarTests.cs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index b984d338f5..7ce7530950 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -491,7 +491,7 @@ public CharMap () vScrollBar.Size = GetContentSize ().Height; vScrollBar.Position = Viewport.Y; }; - }; + } private void Handle_MouseEvent (object sender, MouseEventEventArgs e) { diff --git a/UICatalog/Scenarios/ScrollBarDemo.cs b/UICatalog/Scenarios/ScrollBarDemo.cs index e0ff82f86f..c0b2cd4aea 100644 --- a/UICatalog/Scenarios/ScrollBarDemo.cs +++ b/UICatalog/Scenarios/ScrollBarDemo.cs @@ -183,7 +183,7 @@ public override void Main () }; var ckbAutoHideScrollBar = new CheckBox { Y = Pos.Bottom (scrollPosition), Text = "AutoHideScrollBar" }; - ckbAutoHideScrollBar.CheckedStateChanging += (s, e) => scrollBar.AutoHideScrollBar = e.NewValue == CheckState.Checked; + ckbAutoHideScrollBar.CheckedStateChanging += (s, e) => scrollBar.AutoHide = e.NewValue == CheckState.Checked; view.Add (ckbAutoHideScrollBar); var ckbShowScrollIndicator = new CheckBox { X = Pos.Right (ckbAutoHideScrollBar) + 1, Y = Pos.Bottom (scrollPosition), Text = "ShowScrollIndicator" }; diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs index 10497ffa14..3ba111e448 100644 --- a/UnitTests/Views/ScrollBarTests.cs +++ b/UnitTests/Views/ScrollBarTests.cs @@ -32,7 +32,7 @@ public void AutoHideScrollBar_CheckScrollBarVisibility () Assert.True (scrollBar.ShowScrollIndicator); Assert.True (scrollBar.Visible); - scrollBar.AutoHideScrollBar = false; + scrollBar.AutoHide = false; Assert.True (scrollBar.ShowScrollIndicator); Assert.True (scrollBar.Visible); @@ -228,7 +228,7 @@ public void Constructor_Defaults () Assert.Equal ("Auto(Content,Absolute(1),)", scrollBar.Width!.ToString ()); Assert.Equal ("Auto(Content,Absolute(1),)", scrollBar.Height!.ToString ()); Assert.True (scrollBar.ShowScrollIndicator); - Assert.True (scrollBar.AutoHideScrollBar); + Assert.True (scrollBar.AutoHide); } [Theory] @@ -989,11 +989,11 @@ public void ShowScrollIndicator_CheckScrollBarVisibility () Assert.True (scrollBar.Visible); scrollBar.ShowScrollIndicator = false; - Assert.True (scrollBar.AutoHideScrollBar); + Assert.True (scrollBar.AutoHide); Assert.True (scrollBar.ShowScrollIndicator); Assert.True (scrollBar.Visible); - scrollBar.AutoHideScrollBar = false; + scrollBar.AutoHide = false; Assert.False (scrollBar.ShowScrollIndicator); Assert.False (scrollBar.Visible); From 9075acaab646929e96176612de91bd4e39193f6c Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 4 Sep 2024 12:11:56 +0100 Subject: [PATCH 057/128] Using Visible instead of ShowScrollIndicator. --- Terminal.Gui/Views/Scroll/ScrollBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index 0013f8878a..9c8f856e57 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -168,7 +168,7 @@ private bool CheckVisibility () if (barSize == 0 || barSize >= Size) { - if (ShowScrollIndicator) + if (Visible) { Visible = false; SetNeedsDisplay (); From 3bd46388fcb811a99db399160b5d0fad5c7f219f Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 4 Sep 2024 14:13:56 +0100 Subject: [PATCH 058/128] I think this was already done before. --- Terminal.Gui/Views/Scroll/Scroll.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index a5839feeae..201557322b 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -68,10 +68,10 @@ public int Position return; } - if (SupView is { IsInitialized: false }) + if (SuperViewAsScrollBar is { IsInitialized: false }) { // Ensures a more exactly calculation - SetRelativeLayout (SupView.Frame.Size); + SetRelativeLayout (SuperViewAsScrollBar.Frame.Size); } int barSize = BarSize; @@ -185,7 +185,7 @@ protected virtual CancelEventArgs OnPositionChanging (int currentPos, int n internal void AdjustScroll () { - if (SupView is { }) + if (SuperViewAsScrollBar is { }) { X = Orientation == Orientation.Vertical ? 0 : 1; Y = Orientation == Orientation.Vertical ? 1 : 0; @@ -205,7 +205,7 @@ internal override void OnLayoutComplete (LayoutEventArgs args) AdjustScroll (); } - internal ScrollBar? SupView => SuperView as ScrollBar; + internal ScrollBar? SuperViewAsScrollBar => SuperView as ScrollBar; private int BarSize => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width; From 3701e2171ac45c15a1de6f29865af31f8f606370 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 4 Sep 2024 10:17:02 -0600 Subject: [PATCH 059/128] Makes Scroll clicks move proportionally --- Terminal.Gui/Views/Scroll/Scroll.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 201557322b..7b38921891 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -128,17 +128,21 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; int barSize = BarSize; - (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical - ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) - : new (_slider.Frame.X, _slider.Frame.Right - 1); + (int start, int end) sliderPos = _orientation == Orientation.Vertical + ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) + : new (_slider.Frame.X, _slider.Frame.Right - 1); - if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location < sliderPos.topLeft) + if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location < sliderPos.start) { - Position = Math.Max (Position - barSize, 0); + int distance = sliderPos.start - location; + int scrollAmount = (int)((double)distance / barSize * (Size - barSize)); + Position = Math.Max (Position - scrollAmount, 0); } - else if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location > sliderPos.bottomRight) + else if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location > sliderPos.end) { - Position = Math.Min (Position + barSize, Size - barSize); + int distance = location - sliderPos.end; + int scrollAmount = (int)((double)distance / barSize * (Size - barSize)); + Position = Math.Min (Position + scrollAmount, Size - barSize); } else if ((mouseEvent.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) || (mouseEvent.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) From b495afc448d130c47f6fe6fb858e5b7dd2c6ca6a Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 4 Sep 2024 10:18:53 -0600 Subject: [PATCH 060/128] removed comment --- Terminal.Gui/Views/Scroll/Scroll.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 7b38921891..189413260d 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -165,10 +165,6 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) return base.OnMouseEvent (mouseEvent); } - // TODO: Move this into "ScrollSlider" and override it there. Scroll can then subscribe to _slider.LayoutComplete and call AdjustSlider. - // QUESTION: I've been meaning to add a "View.FrameChanged" event (fired from LayoutComplete only if Frame has changed). Should we do that as part of this PR? - // QUESTION: Note I *did* add "View.ViewportChanged" in a previous PR. - /// Virtual method called when has changed. Raises . protected virtual void OnPositionChanged (int position) { PositionChanged?.Invoke (this, new (in position)); } From da85d581f8430ded1780979c8ff868819f6e4b77 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 4 Sep 2024 18:07:14 +0100 Subject: [PATCH 061/128] Rename to start and end. --- Terminal.Gui/Views/Scroll/Scroll.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 201557322b..c069871084 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -128,15 +128,15 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; int barSize = BarSize; - (int topLeft, int bottomRight) sliderPos = _orientation == Orientation.Vertical - ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) - : new (_slider.Frame.X, _slider.Frame.Right - 1); + (int start, int end) sliderPos = _orientation == Orientation.Vertical + ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) + : new (_slider.Frame.X, _slider.Frame.Right - 1); - if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location < sliderPos.topLeft) + if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location < sliderPos.start) { Position = Math.Max (Position - barSize, 0); } - else if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location > sliderPos.bottomRight) + else if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location > sliderPos.end) { Position = Math.Min (Position + barSize, Size - barSize); } From 20370c4538c9aafb8412fb8cccf514571040b2f8 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 4 Sep 2024 18:08:21 +0100 Subject: [PATCH 062/128] Remove comment. --- Terminal.Gui/Views/Scroll/Scroll.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index c069871084..9439303add 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -161,10 +161,6 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) return base.OnMouseEvent (mouseEvent); } - // TODO: Move this into "ScrollSlider" and override it there. Scroll can then subscribe to _slider.LayoutComplete and call AdjustSlider. - // QUESTION: I've been meaning to add a "View.FrameChanged" event (fired from LayoutComplete only if Frame has changed). Should we do that as part of this PR? - // QUESTION: Note I *did* add "View.ViewportChanged" in a previous PR. - /// Virtual method called when has changed. Raises . protected virtual void OnPositionChanged (int position) { PositionChanged?.Invoke (this, new (in position)); } From f54ded3351fdf398dca808dfc22002179973f738 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 4 Sep 2024 19:41:42 +0100 Subject: [PATCH 063/128] Add KeepContentInAllViewport to Scroll. --- Terminal.Gui/Views/Scroll/Scroll.cs | 52 +++++++++++++++++++++-- Terminal.Gui/Views/Scroll/ScrollBar.cs | 12 +++++- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 19 ++++++--- UnitTests/Views/ScrollBarTests.cs | 43 ++++++++++++++++--- UnitTests/Views/ScrollTests.cs | 34 ++++++++++++++- 5 files changed, 143 insertions(+), 17 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 9439303add..73855ca672 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -33,6 +33,7 @@ public Scroll () private Orientation _orientation; private int _position; private int _size; + private bool _keepContentInAllViewport = true; /// public override void EndInit () @@ -42,6 +43,37 @@ public override void EndInit () AdjustScroll (); } + /// Get or sets if the view-port is kept in all visible area of this + 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 (); + } + } + } + } + /// /// Gets or sets if the Scroll is oriented vertically or horizontally. /// @@ -74,21 +106,21 @@ public int Position SetRelativeLayout (SuperViewAsScrollBar.Frame.Size); } - int barSize = BarSize; + int pos = SetPosition (value); - if (value + barSize > Size) + if (pos == _position) { return; } - CancelEventArgs args = OnPositionChanging (_position, value); + CancelEventArgs args = OnPositionChanging (_position, pos); if (args.Cancel) { return; } - _position = value; + _position = pos; AdjustScroll (); @@ -205,6 +237,18 @@ internal override void OnLayoutComplete (LayoutEventArgs args) 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; diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index 9c8f856e57..ef004dfa4a 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -38,7 +38,10 @@ public ScrollBar () private bool _autoHide = true; private bool _showScrollIndicator = true; - /// Gets or sets whether will be set to if the dimension of the scroll bar is greater than or equal to . + /// + /// Gets or sets whether will be set to if the dimension of the + /// scroll bar is greater than or equal to . + /// public bool AutoHide { get => _autoHide; @@ -52,6 +55,13 @@ public bool AutoHide } } + /// Get or sets if the view-port is kept in all visible area of this + public bool KeepContentInAllViewport + { + get => _scroll.KeepContentInAllViewport; + set => _scroll.KeepContentInAllViewport = value; + } + /// Gets or sets if a scrollbar is vertical or horizontal. public Orientation Orientation { diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index dce3e788c0..89a1998c70 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -28,8 +28,12 @@ public void AdjustSlider () 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 int GetPositionFromSliderLocation (int location) 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 int GetPositionFromSliderLocation (int location) 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 int GetPositionFromSliderLocation (int location) 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; } diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs index 3ba111e448..8d6db7a459 100644 --- a/UnitTests/Views/ScrollBarTests.cs +++ b/UnitTests/Views/ScrollBarTests.cs @@ -231,6 +231,37 @@ public void Constructor_Defaults () 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 void Moving_Mouse_Outside_Host_Ensures_Correct_Location (Orientation orie } [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 void Position_Cannot_Be_Negative_Nor_Greater_Than_Size_Minus_Frame_Length 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] diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index ec36691151..74659e61b6 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -190,6 +190,38 @@ public void Constructor_Defaults () 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 void Position_Cannot_Be_Negative_Nor_Greater_Than_Size_Minus_Frame_Length 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); From 07d7162c8fc9660bd236b308c89e17b025540417 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 4 Sep 2024 19:53:24 +0100 Subject: [PATCH 064/128] Prevent Size being negative. --- Terminal.Gui/Views/Scroll/Scroll.cs | 5 +++++ UnitTests/Views/ScrollBarTests.cs | 9 +++++++++ UnitTests/Views/ScrollTests.cs | 9 +++++++++ 3 files changed, 23 insertions(+) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 73855ca672..d8867c6561 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -145,6 +145,11 @@ public int Size get => _size; set { + if (value == _size || value < 0) + { + return; + } + _size = value; OnSizeChanged (_size); AdjustScroll (); diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs index 8d6db7a459..e318a96d30 100644 --- a/UnitTests/Views/ScrollBarTests.cs +++ b/UnitTests/Views/ScrollBarTests.cs @@ -1031,6 +1031,15 @@ public void ShowScrollIndicator_CheckScrollBarVisibility () scrollBarSuperView.SuperView!.Dispose (); } + [Fact] + public void Size_Cannot_Be_Negative () + { + var scrollBar = new ScrollBar { Height = 10, Size = -1 }; + Assert.Equal (0, scrollBar.Size); + scrollBar.Size = -10; + Assert.Equal (0, scrollBar.Size); + } + [Fact] public void SizeChanged_Event () { diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index 74659e61b6..fe4bedc9ea 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -896,6 +896,15 @@ void Reset () } } + [Fact] + public void Size_Cannot_Be_Negative () + { + var scroll = new Scroll { Height = 10, Size = -1 }; + Assert.Equal (0, scroll.Size); + scroll.Size = -10; + Assert.Equal (0, scroll.Size); + } + [Fact] public void SizeChanged_Event () { From 14e2e3cbb630a12d99720c403fa5b719900b6ec0 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 4 Sep 2024 23:06:17 +0100 Subject: [PATCH 065/128] Fix some KeepContentInAllViewport bugs. --- Terminal.Gui/Views/Scroll/Scroll.cs | 21 +++++++++++++-------- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 11 ++++++++--- UICatalog/Scenarios/ScrollBarDemo.cs | 21 +++++++++++++++++---- UICatalog/Scenarios/ScrollDemo.cs | 10 +++++++++- 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index d8867c6561..46f524a41b 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -54,22 +54,27 @@ public bool KeepContentInAllViewport _keepContentInAllViewport = value; var pos = 0; - if (value && Orientation == Orientation.Horizontal && _position + SuperViewAsScrollBar!.Viewport.Width > Size) + if (value + && Orientation == Orientation.Horizontal + && _position + (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.Viewport.Width : Viewport.Width) > Size) { - pos = Size - SuperViewAsScrollBar.Viewport.Width; + pos = Size - (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.Viewport.Width : Viewport.Width); } - if (value && Orientation == Orientation.Vertical && _position + SuperViewAsScrollBar!.Viewport.Height > Size) + if (value + && Orientation == Orientation.Vertical + && _position + (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.Viewport.Height : Viewport.Height) > Size) { - pos = _size - SuperViewAsScrollBar.Viewport.Height; + pos = _size - (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.Viewport.Height : Viewport.Height); } if (pos != 0) { Position = pos; - SetNeedsDisplay (); - AdjustScroll (); } + + SetNeedsDisplay (); + AdjustScroll (); } } } @@ -175,12 +180,12 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) } else if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location > sliderPos.end) { - Position = Math.Min (Position + barSize, Size - barSize); + Position = Math.Min (Position + barSize, Size - barSize + (KeepContentInAllViewport ? 0 : barSize)); } else if ((mouseEvent.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) || (mouseEvent.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) { - Position = Math.Min (Position + 1, Size - barSize); + Position = Math.Min (Position + 1, Size - barSize + (KeepContentInAllViewport ? 0 : barSize)); } else if ((mouseEvent.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical) || (mouseEvent.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal)) diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 89a1998c70..ad37e58bdf 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -89,8 +89,13 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { if (SuperViewAsScroll.Orientation == Orientation.Vertical) { - Y = Frame.Y + offset < 0 ? 0 : - Frame.Y + offset + Frame.Height > barSize ? Math.Max (barSize - Frame.Height, 0) : Frame.Y + offset; + Y = Frame.Y + offset < 0 + ? 0 + : + Frame.Y + offset + Frame.Height > barSize + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : barSize) + ? + Math.Max (barSize - Frame.Height, 0) + : Frame.Y + offset; SuperViewAsScroll.Position = GetPositionFromSliderLocation (Frame.Y); } @@ -157,7 +162,7 @@ internal int GetPositionFromSliderLocation (int location) if ((SuperViewAsScroll.Orientation == Orientation.Vertical && location + Frame.Height >= scrollSize) || (SuperViewAsScroll.Orientation == Orientation.Horizontal && location + Frame.Width >= scrollSize)) { - return SuperViewAsScroll.Size - scrollSize; + return SuperViewAsScroll.Size - scrollSize + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize); } return (int)Math.Min (Math.Round ((double)(location * SuperViewAsScroll.Size + location) / scrollSize), SuperViewAsScroll.Size - scrollSize); diff --git a/UICatalog/Scenarios/ScrollBarDemo.cs b/UICatalog/Scenarios/ScrollBarDemo.cs index c0b2cd4aea..373e4c5a92 100644 --- a/UICatalog/Scenarios/ScrollBarDemo.cs +++ b/UICatalog/Scenarios/ScrollBarDemo.cs @@ -182,14 +182,27 @@ public override void Main () } }; - var ckbAutoHideScrollBar = new CheckBox { Y = Pos.Bottom (scrollPosition), Text = "AutoHideScrollBar" }; - ckbAutoHideScrollBar.CheckedStateChanging += (s, e) => scrollBar.AutoHide = e.NewValue == CheckState.Checked; - view.Add (ckbAutoHideScrollBar); + var ckbAutoHide = new CheckBox + { Y = Pos.Bottom (scrollPosition), Text = "AutoHideScrollBar", CheckedState = scrollBar.AutoHide ? CheckState.Checked : CheckState.UnChecked }; + ckbAutoHide.CheckedStateChanging += (s, e) => scrollBar.AutoHide = e.NewValue == CheckState.Checked; + view.Add (ckbAutoHide); - var ckbShowScrollIndicator = new CheckBox { X = Pos.Right (ckbAutoHideScrollBar) + 1, Y = Pos.Bottom (scrollPosition), Text = "ShowScrollIndicator" }; + var ckbShowScrollIndicator = new CheckBox + { + X = Pos.Right (ckbAutoHide) + 1, Y = Pos.Bottom (scrollPosition), Text = "ShowScrollIndicator", + CheckedState = scrollBar.ShowScrollIndicator ? CheckState.Checked : CheckState.UnChecked + }; ckbShowScrollIndicator.CheckedStateChanging += (s, e) => scrollBar.ShowScrollIndicator = e.NewValue == CheckState.Checked; view.Add (ckbShowScrollIndicator); + var ckbKeepContentInAllViewport = new CheckBox + { + X = Pos.Right (ckbShowScrollIndicator) + 1, Y = Pos.Bottom (scrollPosition), Text = "KeepContentInAllViewport", + CheckedState = scrollBar.KeepContentInAllViewport ? CheckState.Checked : CheckState.UnChecked + }; + ckbKeepContentInAllViewport.CheckedStateChanging += (s, e) => scrollBar.KeepContentInAllViewport = e.NewValue == CheckState.Checked; + view.Add (ckbKeepContentInAllViewport); + var lblSizeChanged = new Label { Y = Pos.Bottom (ckbShowScrollIndicator) + 1 diff --git a/UICatalog/Scenarios/ScrollDemo.cs b/UICatalog/Scenarios/ScrollDemo.cs index 9fdb0226dc..a0cd41fd73 100644 --- a/UICatalog/Scenarios/ScrollDemo.cs +++ b/UICatalog/Scenarios/ScrollDemo.cs @@ -182,9 +182,17 @@ public override void Main () } }; + var ckbKeepContentInAllViewport = new CheckBox + { + Y = Pos.Bottom (scrollPosition), Text = "KeepContentInAllViewport", + CheckedState = scroll.KeepContentInAllViewport ? CheckState.Checked : CheckState.UnChecked + }; + ckbKeepContentInAllViewport.CheckedStateChanging += (s, e) => scroll.KeepContentInAllViewport = e.NewValue == CheckState.Checked; + view.Add (ckbKeepContentInAllViewport); + var lblSizeChanged = new Label { - Y = Pos.Bottom (lblPosition) + 1 + Y = Pos.Bottom (ckbKeepContentInAllViewport) + 1 }; view.Add (lblSizeChanged); From dbbde3e13ce7c16c695926dd26d9ea182f236b36 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 4 Sep 2024 23:07:10 +0100 Subject: [PATCH 066/128] Using GetContentSize. --- Terminal.Gui/Views/Scroll/Scroll.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 46f524a41b..826d49e8de 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -56,16 +56,16 @@ public bool KeepContentInAllViewport if (value && Orientation == Orientation.Horizontal - && _position + (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.Viewport.Width : Viewport.Width) > Size) + && _position + (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.GetContentSize ().Width : GetContentSize ().Width) > Size) { - pos = Size - (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.Viewport.Width : Viewport.Width); + pos = Size - (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.GetContentSize ().Width : GetContentSize ().Width); } if (value && Orientation == Orientation.Vertical - && _position + (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.Viewport.Height : Viewport.Height) > Size) + && _position + (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.GetContentSize ().Height : GetContentSize ().Height) > Size) { - pos = _size - (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.Viewport.Height : Viewport.Height); + pos = _size - (SuperViewAsScrollBar is { } ? SuperViewAsScrollBar.GetContentSize ().Height : GetContentSize ().Height); } if (pos != 0) From bf3e9b2ac19fc792921bb3766166dfa6c9928fd1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 5 Sep 2024 11:57:44 +0100 Subject: [PATCH 067/128] Fixes #3729. ProcessContinuousButtonPressedAsync is using a stale MouseEvent. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 8fb1d52e8c..c0034d6c2f 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1811,12 +1811,6 @@ private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag) int delay = startDelay; while (_isButtonPressed) { - var me = new MouseEvent - { - Position = _pointMove, - Flags = mouseFlag - }; - // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. View view = Application.WantContinuousButtonPressedView; @@ -1831,6 +1825,12 @@ private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag) } await Task.Delay (delay); + var me = new MouseEvent + { + Position = _pointMove, + Flags = mouseFlag + }; + //Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}"); if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { From cccbbc2b225d6c851e37c84e45c3e2a16c4473f8 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 5 Sep 2024 18:39:58 +0100 Subject: [PATCH 068/128] Typo. --- Terminal.Gui/Views/Scroll/ScrollBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index ef004dfa4a..d9916e332e 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -55,7 +55,7 @@ public bool AutoHide } } - /// Get or sets if the view-port is kept in all visible area of this + /// Get or sets if the view-port is kept in all visible area of this . public bool KeepContentInAllViewport { get => _scroll.KeepContentInAllViewport; From 8d346a8d5c90d68b8f25a6d69a611fb987cb3267 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 5 Sep 2024 23:46:43 +0100 Subject: [PATCH 069/128] Implement @tig scroll bars and fix some bugs. --- Terminal.Gui/View/View.ScrollBars.cs | 167 ++++++++++++++++++++++ Terminal.Gui/View/View.cs | 3 + Terminal.Gui/Views/Scroll/Scroll.cs | 4 +- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 49 +++++-- UICatalog/Scenarios/ContentScrolling.cs | 66 +++++++-- UnitTests/Views/ScrollBarTests.cs | 20 +-- UnitTests/Views/ScrollSliderTests.cs | 5 +- UnitTests/Views/ScrollTests.cs | 19 +-- 8 files changed, 290 insertions(+), 43 deletions(-) create mode 100644 Terminal.Gui/View/View.ScrollBars.cs diff --git a/Terminal.Gui/View/View.ScrollBars.cs b/Terminal.Gui/View/View.ScrollBars.cs new file mode 100644 index 0000000000..7ddcddc368 --- /dev/null +++ b/Terminal.Gui/View/View.ScrollBars.cs @@ -0,0 +1,167 @@ +#nullable enable +namespace Terminal.Gui; + +public partial class View +{ + private Lazy _horizontalScrollBar; + private Lazy _verticalScrollBar; + + /// + /// Initializes the ScrollBars of the View. Called by the constructor. + /// + 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 += (sender, args) => + { + Padding.Thickness = Padding.Thickness with + { + Bottom = scrollBar.Visible ? Padding.Thickness.Bottom + 1 : 0 + }; + + scrollBar.PositionChanged += (sender, args) => + { + Viewport = Viewport with { X = args.CurrentValue }; + }; + + scrollBar.VisibleChanged += (sender, args) => + { + 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 += (sender, args) => + { + Padding.Thickness = Padding.Thickness with + { + Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : 0 + }; + + scrollBar.PositionChanged += (sender, args) => + { + Viewport = Viewport with { Y = args.CurrentValue }; + }; + + scrollBar.VisibleChanged += (sender, args) => + { + Padding.Thickness = Padding.Thickness with + { + Right = scrollBar.Visible + ? Padding.Thickness.Right + 1 + : Padding.Thickness.Right - 1 + }; + }; + }; + + return scrollBar; + }); + + ViewportChanged += (sender, args) => + { + if (_verticalScrollBar.IsValueCreated) + { + _verticalScrollBar.Value.Position = Viewport.Y; + } + + if (_horizontalScrollBar.IsValueCreated) + { + _horizontalScrollBar.Value.Position = Viewport.X; + } + }; + + ContentSizeChanged += (sender, args) => + { + if (_verticalScrollBar.IsValueCreated) + { + _verticalScrollBar.Value.Size = GetContentSize ().Height; + } + if (_horizontalScrollBar.IsValueCreated) + { + _horizontalScrollBar.Value.Size = GetContentSize ().Width; + } + }; + } + + /// + /// + public ScrollBar? HorizontalScrollBar => _horizontalScrollBar.Value; + + /// + /// + public ScrollBar? VerticalScrollBar => _verticalScrollBar.Value; + + /// + /// Clean up the ScrollBars of the View. Called by View.Dispose. + /// + private void DisposeScrollBars () + { + if (_horizontalScrollBar.IsValueCreated) + { + Padding?.Remove (_horizontalScrollBar.Value); + _horizontalScrollBar.Value.Dispose (); + } + + if (_verticalScrollBar.IsValueCreated) + { + Padding?.Remove (_verticalScrollBar.Value); + _verticalScrollBar.Value.Dispose (); + } + } +} diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index f01c9c78d0..68b03404f8 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -135,6 +135,7 @@ protected override void Dispose (bool disposing) DisposeKeyboard (); DisposeAdornments (); + DisposeScrollBars (); for (int i = InternalSubviews.Count - 1; i >= 0; i--) { @@ -185,6 +186,8 @@ public View () //SetupMouse (); SetupText (); + + SetupScrollBars (); } /// diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 826d49e8de..59bc78b034 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -33,7 +33,7 @@ public Scroll () private Orientation _orientation; private int _position; private int _size; - private bool _keepContentInAllViewport = true; + private bool _keepContentInAllViewport; /// public override void EndInit () @@ -251,7 +251,7 @@ private int SetPosition (int position) { int barSize = BarSize; - if (position + barSize > Size) + if (position + barSize > Size + (KeepContentInAllViewport ? 0 : barSize)) { return KeepContentInAllViewport ? Math.Max (Size - barSize, 0) : Math.Max (Size - 1, 0); } diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index ad37e58bdf..2a2acc27c4 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -91,18 +91,19 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { Y = Frame.Y + offset < 0 ? 0 - : - Frame.Y + offset + Frame.Height > barSize + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : barSize) - ? - Math.Max (barSize - Frame.Height, 0) + : Frame.Y + offset + Frame.Height > barSize + ? Math.Max (barSize - Frame.Height, 0) : Frame.Y + offset; SuperViewAsScroll.Position = GetPositionFromSliderLocation (Frame.Y); } else { - X = Frame.X + offset < 0 ? 0 : - Frame.X + offset + Frame.Width > barSize ? Math.Max (barSize - Frame.Width, 0) : Frame.X + offset; + X = Frame.X + offset < 0 + ? 0 + : Frame.X + offset + Frame.Width > barSize + ? Math.Max (barSize - Frame.Width, 0) + : Frame.X + offset; SuperViewAsScroll.Position = GetPositionFromSliderLocation (Frame.X); } @@ -119,7 +120,9 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) else if ((mouseEvent.Flags == MouseFlags.WheeledDown && SuperViewAsScroll.Orientation == Orientation.Vertical) || (mouseEvent.Flags == MouseFlags.WheeledRight && SuperViewAsScroll.Orientation == Orientation.Horizontal)) { - SuperViewAsScroll.Position = Math.Min (SuperViewAsScroll.Position + 1, SuperViewAsScroll.Size - barSize); + SuperViewAsScroll.Position = Math.Min ( + SuperViewAsScroll.Position + 1, + SuperViewAsScroll.KeepContentInAllViewport ? SuperViewAsScroll.Size - barSize : SuperViewAsScroll.Size - 1); } else if ((mouseEvent.Flags == MouseFlags.WheeledUp && SuperViewAsScroll.Orientation == Orientation.Vertical) || (mouseEvent.Flags == MouseFlags.WheeledLeft && SuperViewAsScroll.Orientation == Orientation.Horizontal)) @@ -162,10 +165,17 @@ internal int GetPositionFromSliderLocation (int location) if ((SuperViewAsScroll.Orientation == Orientation.Vertical && location + Frame.Height >= scrollSize) || (SuperViewAsScroll.Orientation == Orientation.Horizontal && location + Frame.Width >= scrollSize)) { - return SuperViewAsScroll.Size - scrollSize + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize); + return Math.Min ( + Math.Max (SuperViewAsScroll.Position + location, 0), + SuperViewAsScroll.KeepContentInAllViewport ? SuperViewAsScroll.Size - scrollSize : SuperViewAsScroll.Size - 1); } - return (int)Math.Min (Math.Round ((double)(location * SuperViewAsScroll.Size + location) / scrollSize), SuperViewAsScroll.Size - scrollSize); + return (int)Math.Min ( + Math.Round ( + (double)(location * (SuperViewAsScroll.Size + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize)) + + location) + / scrollSize), + SuperViewAsScroll.Size - scrollSize + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize)); } internal (int Location, int Dimension) GetSliderLocationDimensionFromPosition () @@ -183,18 +193,31 @@ internal int GetPositionFromSliderLocation (int location) if (SuperViewAsScroll.Size > 0) { - dimension = (int)Math.Min (Math.Max (Math.Ceiling ((double)scrollSize * scrollSize / SuperViewAsScroll.Size), 1), scrollSize); + dimension = (int)Math.Min ( + Math.Max ( + Math.Ceiling ( + (double)scrollSize + * scrollSize + / (SuperViewAsScroll.Size + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize))), + 1), + scrollSize); // Ensure the Position is valid if (SuperViewAsScroll.Position > 0 && SuperViewAsScroll.Position + scrollSize > SuperViewAsScroll.Size + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize)) { - SuperViewAsScroll.Position = SuperViewAsScroll.Size - scrollSize; + SuperViewAsScroll.Position = SuperViewAsScroll.KeepContentInAllViewport ? SuperViewAsScroll.Size - scrollSize : SuperViewAsScroll.Size - 1; } - location = (int)Math.Min (Math.Round ((double)SuperViewAsScroll.Position * scrollSize / (SuperViewAsScroll.Size + 1)), scrollSize - dimension); + location = (int)Math.Min ( + Math.Round ( + (double)SuperViewAsScroll.Position + * scrollSize + / (SuperViewAsScroll.Size + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize))), + scrollSize - dimension); - if (SuperViewAsScroll.Position == SuperViewAsScroll.Size - scrollSize && location + dimension < scrollSize) + if (SuperViewAsScroll.Position == SuperViewAsScroll.Size - scrollSize + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize) + && location + dimension < scrollSize) { location = scrollSize - dimension; } diff --git a/UICatalog/Scenarios/ContentScrolling.cs b/UICatalog/Scenarios/ContentScrolling.cs index 543432a4c4..c812445810 100644 --- a/UICatalog/Scenarios/ContentScrolling.cs +++ b/UICatalog/Scenarios/ContentScrolling.cs @@ -123,11 +123,12 @@ public override void Main () Width = Dim.Fill (), Height = Dim.Fill () }; + app.Add (view); // Add Scroll Setting UI to Padding - view.Padding.Thickness = new (0, 3, 0, 0); - view.Padding.ColorScheme = Colors.ColorSchemes ["Error"]; + view.Padding.Thickness = view.Padding.Thickness with { Top = view.Padding.Thickness.Top + 4 }; + //view.Padding.ColorScheme = Colors.ColorSchemes ["Error"]; var cbAllowNegativeX = new CheckBox { @@ -135,7 +136,7 @@ public override void Main () Y = 0, CanFocus = false }; - cbAllowNegativeX.CheckedState = view.ViewportSettings.HasFlag(ViewportSettings.AllowNegativeX) ? CheckState.Checked : CheckState.UnChecked; + cbAllowNegativeX.CheckedState = view.ViewportSettings.HasFlag (ViewportSettings.AllowNegativeX) ? CheckState.Checked : CheckState.UnChecked; cbAllowNegativeX.CheckedStateChanging += AllowNegativeX_Toggle; void AllowNegativeX_Toggle (object sender, CancelEventArgs e) @@ -159,7 +160,7 @@ void AllowNegativeX_Toggle (object sender, CancelEventArgs e) Y = 0, CanFocus = false }; - cbAllowNegativeY.CheckedState = view.ViewportSettings.HasFlag(ViewportSettings.AllowNegativeY) ? CheckState.Checked : CheckState.UnChecked; + cbAllowNegativeY.CheckedState = view.ViewportSettings.HasFlag (ViewportSettings.AllowNegativeY) ? CheckState.Checked : CheckState.UnChecked; cbAllowNegativeY.CheckedStateChanging += AllowNegativeY_Toggle; void AllowNegativeY_Toggle (object sender, CancelEventArgs e) @@ -182,7 +183,7 @@ void AllowNegativeY_Toggle (object sender, CancelEventArgs e) Y = Pos.Bottom (cbAllowNegativeX), CanFocus = false }; - cbAllowXGreaterThanContentWidth.CheckedState = view.ViewportSettings.HasFlag(ViewportSettings.AllowXGreaterThanContentWidth) ? CheckState.Checked : CheckState.UnChecked; + cbAllowXGreaterThanContentWidth.CheckedState = view.ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth) ? CheckState.Checked : CheckState.UnChecked; cbAllowXGreaterThanContentWidth.CheckedStateChanging += AllowXGreaterThanContentWidth_Toggle; void AllowXGreaterThanContentWidth_Toggle (object sender, CancelEventArgs e) @@ -206,7 +207,7 @@ void AllowXGreaterThanContentWidth_Toggle (object sender, CancelEventArgs e) @@ -284,7 +285,7 @@ void ContentSizeHeight_ValueChanged (object sender, CancelEventArgs e) Y = Pos.Top (labelContentSize), CanFocus = false }; - cbClearOnlyVisible.CheckedState = view.ViewportSettings.HasFlag(ViewportSettings.ClearContentOnly) ? CheckState.Checked : CheckState.UnChecked; + cbClearOnlyVisible.CheckedState = view.ViewportSettings.HasFlag (ViewportSettings.ClearContentOnly) ? CheckState.Checked : CheckState.UnChecked; cbClearOnlyVisible.CheckedStateChanging += ClearVisibleContentOnly_Toggle; void ClearVisibleContentOnly_Toggle (object sender, CancelEventArgs e) @@ -321,7 +322,56 @@ void ClipVisibleContentOnly_Toggle (object sender, CancelEventArgs e } } - view.Padding.Add (labelContentSize, contentSizeWidth, labelComma, contentSizeHeight, cbClearOnlyVisible, cbDoNotClipContent); + var cbVerticalScrollBar = new CheckBox + { + Title = "Vertical ScrollBar", + X = 0, + Y = Pos.Bottom (labelContentSize), + CanFocus = false + }; + view.VerticalScrollBar.ShowScrollIndicator = false; + cbVerticalScrollBar.CheckedState = view.VerticalScrollBar.Visible ? CheckState.Checked : CheckState.UnChecked; + cbVerticalScrollBar.CheckedStateChanging += VerticalScrollBar_Toggle; + + void VerticalScrollBar_Toggle (object sender, CancelEventArgs e) + { + view.VerticalScrollBar.ShowScrollIndicator = e.NewValue == CheckState.Checked; + } + + var cbHorizontalScrollBar = new CheckBox + { + Title = "Horizontal ScrollBar", + X = Pos.Right (cbVerticalScrollBar) + 1, + Y = Pos.Bottom (labelContentSize), + CanFocus = false + }; + view.HorizontalScrollBar.ShowScrollIndicator = false; + cbHorizontalScrollBar.CheckedState = view.HorizontalScrollBar.ShowScrollIndicator ? CheckState.Checked : CheckState.UnChecked; + cbHorizontalScrollBar.CheckedStateChanging += HorizontalScrollBar_Toggle; + + void HorizontalScrollBar_Toggle (object sender, CancelEventArgs e) + { + view.HorizontalScrollBar.ShowScrollIndicator = e.NewValue == CheckState.Checked; + } + + var cbAutoHideScrollBars = new CheckBox + { + Title = "Auto-hide ScrollBars", + X = Pos.Right (cbHorizontalScrollBar) + 1, + Y = Pos.Bottom (labelContentSize), + CanFocus = false + }; + view.HorizontalScrollBar.AutoHide = true; + view.VerticalScrollBar.AutoHide = true; + cbAutoHideScrollBars.CheckedState = view.HorizontalScrollBar.AutoHide ? CheckState.Checked : CheckState.UnChecked; + cbAutoHideScrollBars.CheckedStateChanging += AutoHideScrollBars_Toggle; + + void AutoHideScrollBars_Toggle (object sender, CancelEventArgs e) + { + view.HorizontalScrollBar.AutoHide = view.VerticalScrollBar.AutoHide = e.NewValue == CheckState.Checked; + } + + view.Padding.Add (labelContentSize, contentSizeWidth, labelComma, contentSizeHeight, cbClearOnlyVisible, cbDoNotClipContent, cbVerticalScrollBar, cbHorizontalScrollBar, cbAutoHideScrollBars); // Add demo views to show that things work correctly var textField = new TextField { X = 20, Y = 7, Width = 15, Text = "Test TextField" }; diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs index e318a96d30..733262872b 100644 --- a/UnitTests/Views/ScrollBarTests.cs +++ b/UnitTests/Views/ScrollBarTests.cs @@ -245,7 +245,8 @@ public void KeepContentInAllViewport_True_False () top.Add (view); Application.Begin (top); - Assert.True (scrollBar.KeepContentInAllViewport); + 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); @@ -705,7 +706,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit 20, @" ◄░░░░██░░►")] - public void Mouse_On_The_Slider ( + public void Mouse_On_The_Slider_KeepContentInAllViewport_True ( Orientation orientation, int size, int position, @@ -722,7 +723,8 @@ string expectedOut Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Orientation = orientation, - Size = size, Position = position + Size = size, Position = position, + KeepContentInAllViewport = true }; var top = new Toplevel (); top.Add (scrollBar); @@ -782,12 +784,12 @@ string expectedOut [AutoInitShutdown] [InlineData (Orientation.Vertical)] [InlineData (Orientation.Horizontal)] - public void Mouse_Pressed_On_ScrollButton_Changes_Position (Orientation orientation) + public void Mouse_Pressed_On_ScrollButton_Changes_Position_KeepContentInAllViewport_True (Orientation orientation) { var scrollBar = new ScrollBar { X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, - Orientation = orientation + Orientation = orientation, KeepContentInAllViewport = true }; var top = new Toplevel (); top.Add (scrollBar); @@ -881,9 +883,9 @@ public void Moving_Mouse_Outside_Host_Ensures_Correct_Location (Orientation orie [Theory] [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) + public void Position_Cannot_Be_Negative_Nor_Greater_Than_Size_Minus_Frame_Length_KeepContentInAllViewport_True (Orientation orientation, int size, int expectedPos1, int expectedPos2) { - var scrollBar = new ScrollBar { Orientation = orientation, Height = 10, Size = size }; + var scrollBar = new ScrollBar { Orientation = orientation, Height = 10, Size = size, KeepContentInAllViewport = true }; Assert.Equal (0, scrollBar.Position); scrollBar.Position = -1; @@ -926,12 +928,12 @@ public void PositionChanging_Cancelable_And_PositionChanged_Events () } [Fact] - public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position_Was_Really_Changed () + public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position_Was_Really_Changed_KeepContentInAllViewport_True () { var changing = 0; var cancel = false; var changed = 0; - var scrollBar = new ScrollBar { Height = 10, Size = 20 }; + var scrollBar = new ScrollBar { Height = 10, Size = 20, KeepContentInAllViewport = true }; scrollBar.PositionChanging += Scroll_PositionChanging; scrollBar.PositionChanged += Scroll_PositionChanged; diff --git a/UnitTests/Views/ScrollSliderTests.cs b/UnitTests/Views/ScrollSliderTests.cs index fd37fd17b2..373a4d1b32 100644 --- a/UnitTests/Views/ScrollSliderTests.cs +++ b/UnitTests/Views/ScrollSliderTests.cs @@ -14,7 +14,7 @@ public class ScrollSliderTests [InlineData (Orientation.Vertical, 26, 236, 27, 210)] [InlineData (Orientation.Vertical, 37, 236, 2, 13)] [InlineData (Orientation.Vertical, 42, 236, 29, 164)] - public void Test_Position_Location_Consistency (Orientation orientation, int scrollLength, int size, int location, int expectedPosition) + public void Test_Position_Location_Consistency_KeepContentInAllViewport_True (Orientation orientation, int scrollLength, int size, int location, int expectedPosition) { // Arrange Scroll scroll = new () @@ -22,7 +22,8 @@ public void Test_Position_Location_Consistency (Orientation orientation, int scr Orientation = orientation, Width = orientation == Orientation.Vertical ? 1 : scrollLength, Height = orientation == Orientation.Vertical ? scrollLength : 1, - Size = size + Size = size, + KeepContentInAllViewport = true }; scroll.BeginInit (); diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index fe4bedc9ea..eeb01e6d26 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -190,17 +190,17 @@ public void Constructor_Defaults () Assert.Equal (Orientation.Vertical, scroll.Orientation); Assert.Equal (0, scroll.Size); Assert.Equal (0, scroll.Position); - Assert.True (scroll.KeepContentInAllViewport); + Assert.False (scroll.KeepContentInAllViewport); } [Fact] [AutoInitShutdown] - public void KeepContentInAllViewport_True_False () + public void KeepContentInAllViewport_True_False_KeepContentInAllViewport_True () { 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 }; + var scroll = new Scroll { Width = 2, Height = Dim.Fill (), Size = view.GetContentSize ().Height, KeepContentInAllViewport = true }; scroll.PositionChanged += (_, e) => view.Viewport = view.Viewport with { Y = e.CurrentValue }; view.Padding.Add (scroll); var top = new Toplevel (); @@ -667,7 +667,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit 16, @" ░░░░███░░░")] - public void Mouse_On_The_Slider ( + public void Mouse_On_The_Slider_KeepContentInAllViewport_True ( Orientation orientation, int size, int position, @@ -684,7 +684,8 @@ string expectedOut Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Orientation = orientation, - Size = size, Position = position + Size = size, Position = position, + KeepContentInAllViewport = true }; var top = new Toplevel (); top.Add (scroll); @@ -782,9 +783,9 @@ public void Moving_Mouse_Outside_Host_Ensures_Correct_Location (Orientation orie [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) + public void Position_Cannot_Be_Negative_Nor_Greater_Than_Size_Minus_Frame_Length_KeepContentInAllViewport_True (Orientation orientation, int size, int expectedPos) { - var scroll = new Scroll { Orientation = orientation, Height = 10, Size = size }; + var scroll = new Scroll { Orientation = orientation, Height = 10, Size = size, KeepContentInAllViewport = true }; Assert.Equal (0, scroll.Position); scroll.Position = -1; @@ -827,12 +828,12 @@ public void PositionChanging_Cancelable_And_PositionChanged_Events () } [Fact] - public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position_Was_Really_Changed () + public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position_Was_Really_Changed_KeepContentInAllViewport_True () { var changing = 0; var cancel = false; var changed = 0; - var scroll = new Scroll { Height = 10, Size = 20 }; + var scroll = new Scroll { Height = 10, Size = 20, KeepContentInAllViewport = true }; scroll.PositionChanging += Scroll_PositionChanging; scroll.PositionChanged += Scroll_PositionChanged; From 84225fcdcbfe973a82815dfafc371af5108d6bf0 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 6 Sep 2024 00:22:02 +0100 Subject: [PATCH 070/128] Fix unit test. --- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 4 +--- UnitTests/Views/ScrollSliderTests.cs | 7 ++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 2a2acc27c4..992982122d 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -165,9 +165,7 @@ internal int GetPositionFromSliderLocation (int location) if ((SuperViewAsScroll.Orientation == Orientation.Vertical && location + Frame.Height >= scrollSize) || (SuperViewAsScroll.Orientation == Orientation.Horizontal && location + Frame.Width >= scrollSize)) { - return Math.Min ( - Math.Max (SuperViewAsScroll.Position + location, 0), - SuperViewAsScroll.KeepContentInAllViewport ? SuperViewAsScroll.Size - scrollSize : SuperViewAsScroll.Size - 1); + return SuperViewAsScroll.Size - scrollSize + (SuperViewAsScroll.KeepContentInAllViewport ? 0 : scrollSize); } return (int)Math.Min ( diff --git a/UnitTests/Views/ScrollSliderTests.cs b/UnitTests/Views/ScrollSliderTests.cs index 373a4d1b32..54804dd867 100644 --- a/UnitTests/Views/ScrollSliderTests.cs +++ b/UnitTests/Views/ScrollSliderTests.cs @@ -43,8 +43,8 @@ public void Test_Position_Location_Consistency_KeepContentInAllViewport_True (Or // Randomized Test for more extensive testing [Theory] - [InlineData (Orientation.Vertical, 26, 236, 5)] - public void Test_Position_Location_Consistency_Random (Orientation orientation, int scrollLength, int size, int testCount) + [InlineData (Orientation.Vertical, true, 26, 236, 5)] + public void Test_Position_Location_Consistency_Random (Orientation orientation, bool keepContentInAllViewport, int scrollLength, int size, int testCount) { var random = new Random (); @@ -53,7 +53,8 @@ public void Test_Position_Location_Consistency_Random (Orientation orientation, Orientation = orientation, Width = orientation == Orientation.Vertical ? 1 : scrollLength, Height = orientation == Orientation.Vertical ? scrollLength : 1, - Size = size + Size = size, + KeepContentInAllViewport = keepContentInAllViewport }; scroll.BeginInit (); From bd51356b56a1f7ac4b00b49c4b673bd068066a7b Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 6 Sep 2024 00:26:46 +0100 Subject: [PATCH 071/128] Add unit test for KeepContentInAllViewport as false. --- UnitTests/Views/ScrollSliderTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/UnitTests/Views/ScrollSliderTests.cs b/UnitTests/Views/ScrollSliderTests.cs index 54804dd867..9dabbb67ed 100644 --- a/UnitTests/Views/ScrollSliderTests.cs +++ b/UnitTests/Views/ScrollSliderTests.cs @@ -44,6 +44,7 @@ public void Test_Position_Location_Consistency_KeepContentInAllViewport_True (Or // Randomized Test for more extensive testing [Theory] [InlineData (Orientation.Vertical, true, 26, 236, 5)] + [InlineData (Orientation.Vertical, false, 26, 236, 5)] public void Test_Position_Location_Consistency_Random (Orientation orientation, bool keepContentInAllViewport, int scrollLength, int size, int testCount) { var random = new Random (); From 66ec2de3229c7b5e09ed1d39e8feaa5d12f5e4d4 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 6 Sep 2024 00:37:17 +0100 Subject: [PATCH 072/128] Fix more unit tests that were used with KeepContentInAllViewport as true and now is false by default. --- UnitTests/Views/ScrollBarTests.cs | 14 ++++++++------ UnitTests/Views/ScrollTests.cs | 19 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs index 733262872b..048eae1077 100644 --- a/UnitTests/Views/ScrollBarTests.cs +++ b/UnitTests/Views/ScrollBarTests.cs @@ -153,7 +153,7 @@ public void AutoHideScrollBar_CheckScrollBarVisibility () ◄░░██░░░░►", @" ◄░█░░░░░░►")] - public void Changing_Position_Size_Orientation_Draws_Correctly ( + public void Changing_Position_Size_Orientation_Draws_Correctly_KeepContentInAllViewport_True ( int size, string firstVertExpected, string middleVertExpected, @@ -169,7 +169,8 @@ string sizeHoriExpected { Orientation = Orientation.Vertical, Size = size, - Height = 10 + Height = 10, + KeepContentInAllViewport = true }; var top = new Toplevel (); top.Add (scrollBar); @@ -341,14 +342,15 @@ public void KeepContentInAllViewport_True_False () 18, @" ◄░░░░██░░►")] - public void Mouse_On_The_Container (Orientation orientation, int size, int position, int location, string output, int expectedPos, string expectedOut) + public void Mouse_On_The_Container_KeepContentInAllViewport_True (Orientation orientation, int size, int position, int location, string output, int expectedPos, string expectedOut) { var scrollBar = new ScrollBar { Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Orientation = orientation, Size = size, - Position = position + Position = position, + KeepContentInAllViewport = true }; var top = new Toplevel (); top.Add (scrollBar); @@ -843,12 +845,12 @@ public void Mouse_Pressed_On_ScrollButton_Changes_Position_KeepContentInAllViewp [AutoInitShutdown] [InlineData (Orientation.Vertical)] [InlineData (Orientation.Horizontal)] - public void Moving_Mouse_Outside_Host_Ensures_Correct_Location (Orientation orientation) + public void Moving_Mouse_Outside_Host_Ensures_Correct_Location_KeepContentInAllViewport_True (Orientation orientation) { var scrollBar = new ScrollBar { X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, - Position = 5, Orientation = orientation + Position = 5, Orientation = orientation, KeepContentInAllViewport = true }; var top = new Toplevel (); top.Add (scrollBar); diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index eeb01e6d26..25c119c29c 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -117,7 +117,7 @@ public class ScrollTests ░░███░░░░░", @" ░██░░░░░░░")] - public void Changing_Position_Size_Orientation_Draws_Correctly ( + public void Changing_Position_Size_Orientation_Draws_Correctly_KeepContentInAllViewport_True ( int size, string firstVertExpected, string middleVertExpected, @@ -133,7 +133,8 @@ string sizeHoriExpected { Orientation = Orientation.Vertical, Size = size, - Height = 10 + Height = 10, + KeepContentInAllViewport = true }; var top = new Toplevel (); top.Add (scroll); @@ -302,14 +303,15 @@ public void KeepContentInAllViewport_True_False_KeepContentInAllViewport_True () 20, @" ░░░░░███░░")] - public void Mouse_On_The_Container (Orientation orientation, int size, int position, int location, string output, int expectedPos, string expectedOut) + public void Mouse_On_The_Container_KeepContentInAllViewport_True (Orientation orientation, int size, int position, int location, string output, int expectedPos, string expectedOut) { var scroll = new Scroll { Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Orientation = orientation, Size = size, - Position = position + Position = position, + KeepContentInAllViewport = true }; var top = new Toplevel (); top.Add (scroll); @@ -744,12 +746,12 @@ string expectedOut [AutoInitShutdown] [InlineData (Orientation.Vertical)] [InlineData (Orientation.Horizontal)] - public void Moving_Mouse_Outside_Host_Ensures_Correct_Location (Orientation orientation) + public void Moving_Mouse_Outside_Host_Ensures_Correct_Location_KeepContentInAllViewport_True (Orientation orientation) { var scroll = new Scroll { X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, - Position = 5, Orientation = orientation + Position = 5, Orientation = orientation, KeepContentInAllViewport = true }; var top = new Toplevel (); top.Add (scroll); @@ -972,7 +974,7 @@ public void SizeChanged_Event () │████░░░░│ │████░░░░│ └────────┘")] - public void Vertical_Horizontal_Draws_Correctly (int sizeWidth, int sizeHeight, int widthHeight, Orientation orientation, string expected) + public void Vertical_Horizontal_Draws_Correctly_KeepContentInAllViewport_True (int sizeWidth, int sizeHeight, int widthHeight, Orientation orientation, string expected) { var super = new Window { Id = "super", Width = Dim.Fill (), Height = Dim.Fill () }; var top = new Toplevel (); @@ -983,7 +985,8 @@ public void Vertical_Horizontal_Draws_Correctly (int sizeWidth, int sizeHeight, Orientation = orientation, Size = orientation == Orientation.Vertical ? sizeHeight * 2 : sizeWidth * 2, Width = orientation == Orientation.Vertical ? widthHeight : Dim.Fill (), - Height = orientation == Orientation.Vertical ? Dim.Fill () : widthHeight + Height = orientation == Orientation.Vertical ? Dim.Fill () : widthHeight, + KeepContentInAllViewport = true }; super.Add (scroll); From 630638e5a8742d9d0c8c47ff2574a659faa5b46a Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 6 Sep 2024 00:50:13 +0100 Subject: [PATCH 073/128] Fix nullable warnings. --- Terminal.Gui/View/View.ScrollBars.cs | 51 +++++++++++++++------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/Terminal.Gui/View/View.ScrollBars.cs b/Terminal.Gui/View/View.ScrollBars.cs index 7ddcddc368..42809e4ba1 100644 --- a/Terminal.Gui/View/View.ScrollBars.cs +++ b/Terminal.Gui/View/View.ScrollBars.cs @@ -36,19 +36,19 @@ private void SetupScrollBars () Padding?.Add (scrollBar); - scrollBar.Initialized += (sender, args) => + scrollBar.Initialized += (_, _) => { - Padding.Thickness = Padding.Thickness with + Padding!.Thickness = Padding.Thickness with { Bottom = scrollBar.Visible ? Padding.Thickness.Bottom + 1 : 0 }; - scrollBar.PositionChanged += (sender, args) => + scrollBar.PositionChanged += (_, args) => { Viewport = Viewport with { X = args.CurrentValue }; }; - scrollBar.VisibleChanged += (sender, args) => + scrollBar.VisibleChanged += (_, _) => { Padding.Thickness = Padding.Thickness with { @@ -87,33 +87,36 @@ private void SetupScrollBars () Padding?.Add (scrollBar); - scrollBar.Initialized += (sender, args) => + scrollBar.Initialized += (_, _) => { - Padding.Thickness = Padding.Thickness with - { - Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : 0 - }; - - scrollBar.PositionChanged += (sender, args) => - { - Viewport = Viewport with { Y = args.CurrentValue }; - }; - - scrollBar.VisibleChanged += (sender, args) => + if (Padding is { }) { Padding.Thickness = Padding.Thickness with { - Right = scrollBar.Visible - ? Padding.Thickness.Right + 1 - : Padding.Thickness.Right - 1 + Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : 0 }; - }; + + scrollBar.PositionChanged += (_, args) => + { + Viewport = Viewport with { Y = args.CurrentValue }; + }; + + scrollBar.VisibleChanged += (_, _) => + { + Padding.Thickness = Padding.Thickness with + { + Right = scrollBar.Visible + ? Padding.Thickness.Right + 1 + : Padding.Thickness.Right - 1 + }; + }; + } }; return scrollBar; }); - ViewportChanged += (sender, args) => + ViewportChanged += (_, _) => { if (_verticalScrollBar.IsValueCreated) { @@ -126,7 +129,7 @@ private void SetupScrollBars () } }; - ContentSizeChanged += (sender, args) => + ContentSizeChanged += (_, _) => { if (_verticalScrollBar.IsValueCreated) { @@ -141,11 +144,11 @@ private void SetupScrollBars () /// /// - public ScrollBar? HorizontalScrollBar => _horizontalScrollBar.Value; + public ScrollBar HorizontalScrollBar => _horizontalScrollBar.Value; /// /// - public ScrollBar? VerticalScrollBar => _verticalScrollBar.Value; + public ScrollBar VerticalScrollBar => _verticalScrollBar.Value; /// /// Clean up the ScrollBars of the View. Called by View.Dispose. From ae7a86f9a953c2ce4a87e81dc3fc15f2f31f2efc Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 6 Sep 2024 16:43:53 +0100 Subject: [PATCH 074/128] Fix ScrollBar that was returning more 2 position at end. --- Terminal.Gui/Views/Scroll/Scroll.cs | 4 ++-- UnitTests/Views/ScrollBarTests.cs | 30 ++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 59bc78b034..e97650bf23 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -251,9 +251,9 @@ private int SetPosition (int position) { int barSize = BarSize; - if (position + barSize > Size + (KeepContentInAllViewport ? 0 : barSize)) + if (position + barSize > Size + (KeepContentInAllViewport ? 0 : barSize) - (SuperViewAsScrollBar is { } ? 2 : 0)) { - return KeepContentInAllViewport ? Math.Max (Size - barSize, 0) : Math.Max (Size - 1, 0); + return KeepContentInAllViewport ? Math.Max (Size - barSize - (SuperViewAsScrollBar is { } ? 2 : 0), 0) : Math.Max (Size - 1, 0); } return position; diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs index 048eae1077..b1fc3391b1 100644 --- a/UnitTests/Views/ScrollBarTests.cs +++ b/UnitTests/Views/ScrollBarTests.cs @@ -474,7 +474,7 @@ public void Mouse_On_The_Container_KeepContentInAllViewport_True (Orientation or █ ▼", MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 12, + 10, @" ▲ ░ @@ -516,7 +516,7 @@ public void Mouse_On_The_Container_KeepContentInAllViewport_True (Orientation or █ ▼", MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 12, + 10, @" ▲ ░ @@ -537,7 +537,7 @@ public void Mouse_On_The_Container_KeepContentInAllViewport_True (Orientation or @" ◄░░░░████►", MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 12, + 10, @" ◄░░░░████►")] [InlineData ( @@ -803,7 +803,7 @@ public void Mouse_Pressed_On_ScrollButton_Changes_Position_KeepContentInAllViewp Assert.Equal (0, scrollBar.Position); // ScrollButton increase - for (var i = 0; i < 13; i++) + for (var i = 0; i < 11; i++) { Application.OnMouseEvent ( new () @@ -811,7 +811,7 @@ public void Mouse_Pressed_On_ScrollButton_Changes_Position_KeepContentInAllViewp Position = orientation == Orientation.Vertical ? new (10, 19) : new (19, 10), Flags = MouseFlags.Button1Pressed }); - if (i < 12) + if (i < 10) { Assert.Equal (i + 1, scrollBar.Position); } @@ -825,7 +825,7 @@ public void Mouse_Pressed_On_ScrollButton_Changes_Position_KeepContentInAllViewp } } - for (var i = 12; i > -1; i--) + for (var i = 10; i > -1; i--) { Application.OnMouseEvent (new () { Position = new (10, 10), Flags = MouseFlags.Button1Pressed }); @@ -883,8 +883,8 @@ public void Moving_Mouse_Outside_Host_Ensures_Correct_Location_KeepContentInAllV } [Theory] - [InlineData (Orientation.Vertical, 20, 12, 10)] - [InlineData (Orientation.Vertical, 40, 32, 30)] + [InlineData (Orientation.Vertical, 20, 10, 10)] + [InlineData (Orientation.Vertical, 40, 30, 30)] public void Position_Cannot_Be_Negative_Nor_Greater_Than_Size_Minus_Frame_Length_KeepContentInAllViewport_True (Orientation orientation, int size, int expectedPos1, int expectedPos2) { var scrollBar = new ScrollBar { Orientation = orientation, Height = 10, Size = size, KeepContentInAllViewport = true }; @@ -970,19 +970,19 @@ public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position Reset (); scrollBar.Position = 11; - Assert.Equal (11, scrollBar.Position); - Assert.Equal (1, changing); - Assert.Equal (1, changed); + Assert.Equal (10, scrollBar.Position); + Assert.Equal (0, changing); + Assert.Equal (0, changed); Reset (); scrollBar.Position = 12; - Assert.Equal (12, scrollBar.Position); - Assert.Equal (1, changing); - Assert.Equal (1, changed); + Assert.Equal (10, scrollBar.Position); + Assert.Equal (0, changing); + Assert.Equal (0, changed); Reset (); scrollBar.Position = 13; - Assert.Equal (12, scrollBar.Position); + Assert.Equal (10, scrollBar.Position); Assert.Equal (0, changing); Assert.Equal (0, changed); From c5e886f05d4bc08a31aa82e8950850809d82f063 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 6 Sep 2024 16:46:09 +0100 Subject: [PATCH 075/128] Manipulate ViewportSettings with ScrollBars. --- Terminal.Gui/View/View.Content.cs | 1 + Terminal.Gui/View/View.ScrollBars.cs | 39 +++++++ UICatalog/Scenarios/ContentScrolling.cs | 147 +++++++++++++++++------- 3 files changed, 144 insertions(+), 43 deletions(-) diff --git a/Terminal.Gui/View/View.Content.cs b/Terminal.Gui/View/View.Content.cs index 12851d12cb..fcf76ed7ec 100644 --- a/Terminal.Gui/View/View.Content.cs +++ b/Terminal.Gui/View/View.Content.cs @@ -213,6 +213,7 @@ public ViewportSettings ViewportSettings { // Force set Viewport to cause settings to be applied as needed SetViewport (Viewport); + SetScrollBarsKeepContentInAllViewport (_viewportSettings); } } } diff --git a/Terminal.Gui/View/View.ScrollBars.cs b/Terminal.Gui/View/View.ScrollBars.cs index 42809e4ba1..c37a527a58 100644 --- a/Terminal.Gui/View/View.ScrollBars.cs +++ b/Terminal.Gui/View/View.ScrollBars.cs @@ -167,4 +167,43 @@ private void DisposeScrollBars () _verticalScrollBar.Value.Dispose (); } } + + private void SetScrollBarsKeepContentInAllViewport (ViewportSettings viewportSettings) + { + if (viewportSettings == ViewportSettings.None) + { + _horizontalScrollBar.Value.KeepContentInAllViewport = true; + _verticalScrollBar.Value.KeepContentInAllViewport = true; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeX)) + { + _horizontalScrollBar.Value.AutoHide = false; + _horizontalScrollBar.Value.ShowScrollIndicator = false; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeY)) + { + _verticalScrollBar.Value.AutoHide = false; + _verticalScrollBar.Value.ShowScrollIndicator = false; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeLocation)) + { + _horizontalScrollBar.Value.AutoHide = false; + _horizontalScrollBar.Value.ShowScrollIndicator = false; + _verticalScrollBar.Value.AutoHide = false; + _verticalScrollBar.Value.ShowScrollIndicator = false; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth)) + { + _horizontalScrollBar.Value.KeepContentInAllViewport = false; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowYGreaterThanContentHeight)) + { + _verticalScrollBar.Value.KeepContentInAllViewport = false; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowLocationGreaterThanContentSize)) + { + _horizontalScrollBar.Value.KeepContentInAllViewport = false; + _verticalScrollBar.Value.KeepContentInAllViewport = false; + } + } } diff --git a/UICatalog/Scenarios/ContentScrolling.cs b/UICatalog/Scenarios/ContentScrolling.cs index c812445810..e498f11e9c 100644 --- a/UICatalog/Scenarios/ContentScrolling.cs +++ b/UICatalog/Scenarios/ContentScrolling.cs @@ -137,19 +137,6 @@ public override void Main () CanFocus = false }; cbAllowNegativeX.CheckedState = view.ViewportSettings.HasFlag (ViewportSettings.AllowNegativeX) ? CheckState.Checked : CheckState.UnChecked; - cbAllowNegativeX.CheckedStateChanging += AllowNegativeX_Toggle; - - void AllowNegativeX_Toggle (object sender, CancelEventArgs e) - { - if (e.NewValue == CheckState.Checked) - { - view.ViewportSettings |= ViewportSettings.AllowNegativeX; - } - else - { - view.ViewportSettings &= ~ViewportSettings.AllowNegativeX; - } - } view.Padding.Add (cbAllowNegativeX); @@ -161,19 +148,6 @@ void AllowNegativeX_Toggle (object sender, CancelEventArgs e) CanFocus = false }; cbAllowNegativeY.CheckedState = view.ViewportSettings.HasFlag (ViewportSettings.AllowNegativeY) ? CheckState.Checked : CheckState.UnChecked; - cbAllowNegativeY.CheckedStateChanging += AllowNegativeY_Toggle; - - void AllowNegativeY_Toggle (object sender, CancelEventArgs e) - { - if (e.NewValue == CheckState.Checked) - { - view.ViewportSettings |= ViewportSettings.AllowNegativeY; - } - else - { - view.ViewportSettings &= ~ViewportSettings.AllowNegativeY; - } - } view.Padding.Add (cbAllowNegativeY); @@ -184,7 +158,22 @@ void AllowNegativeY_Toggle (object sender, CancelEventArgs e) CanFocus = false }; cbAllowXGreaterThanContentWidth.CheckedState = view.ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth) ? CheckState.Checked : CheckState.UnChecked; - cbAllowXGreaterThanContentWidth.CheckedStateChanging += AllowXGreaterThanContentWidth_Toggle; + + view.Padding.Add (cbAllowXGreaterThanContentWidth); + + void AllowNegativeX_Toggle (object sender, CancelEventArgs e) + { + if (e.NewValue == CheckState.Checked) + { + view.ViewportSettings |= ViewportSettings.AllowNegativeX; + } + else + { + view.ViewportSettings &= ~ViewportSettings.AllowNegativeX; + } + + SetHorizontalScrollBar (e.NewValue, cbAllowXGreaterThanContentWidth.CheckedState); + } void AllowXGreaterThanContentWidth_Toggle (object sender, CancelEventArgs e) { @@ -196,9 +185,9 @@ void AllowXGreaterThanContentWidth_Toggle (object sender, CancelEventArgs e) + { + if (e.NewValue == CheckState.Checked) + { + view.ViewportSettings |= ViewportSettings.AllowNegativeY; + } + else + { + view.ViewportSettings &= ~ViewportSettings.AllowNegativeY; + } + + SetVerticalScrollBar (e.NewValue, cbAllowYGreaterThanContentHeight.CheckedState); + } void AllowYGreaterThanContentHeight_Toggle (object sender, CancelEventArgs e) { @@ -220,9 +224,9 @@ void AllowYGreaterThanContentHeight_Toggle (object sender, CancelEventArgs e) Title = "Horizontal ScrollBar", X = Pos.Right (cbVerticalScrollBar) + 1, Y = Pos.Bottom (labelContentSize), - CanFocus = false + CanFocus = false, + CheckedState = view.HorizontalScrollBar.ShowScrollIndicator ? CheckState.Checked : CheckState.UnChecked }; view.HorizontalScrollBar.ShowScrollIndicator = false; - cbHorizontalScrollBar.CheckedState = view.HorizontalScrollBar.ShowScrollIndicator ? CheckState.Checked : CheckState.UnChecked; cbHorizontalScrollBar.CheckedStateChanging += HorizontalScrollBar_Toggle; void HorizontalScrollBar_Toggle (object sender, CancelEventArgs e) @@ -354,24 +358,81 @@ void HorizontalScrollBar_Toggle (object sender, CancelEventArgs e) view.HorizontalScrollBar.ShowScrollIndicator = e.NewValue == CheckState.Checked; } - var cbAutoHideScrollBars = new CheckBox + var cbAutoHideVerticalScrollBar = new CheckBox { - Title = "Auto-hide ScrollBars", + Title = "Auto-hide Vertical ScrollBar", X = Pos.Right (cbHorizontalScrollBar) + 1, Y = Pos.Bottom (labelContentSize), - CanFocus = false + CanFocus = false, + CheckedState = view.HorizontalScrollBar.AutoHide ? CheckState.Checked : CheckState.UnChecked }; - view.HorizontalScrollBar.AutoHide = true; view.VerticalScrollBar.AutoHide = true; - cbAutoHideScrollBars.CheckedState = view.HorizontalScrollBar.AutoHide ? CheckState.Checked : CheckState.UnChecked; - cbAutoHideScrollBars.CheckedStateChanging += AutoHideScrollBars_Toggle; + cbAutoHideVerticalScrollBar.CheckedStateChanging += AutoHideVerticalScrollBar_Toggle; + + void AutoHideVerticalScrollBar_Toggle (object sender, CancelEventArgs e) + { + view.VerticalScrollBar.AutoHide = e.NewValue == CheckState.Checked; + } + + var cbAutoHideHorizontalScrollBar = new CheckBox + { + Title = "Auto-hide Horizontal ScrollBar", + X = Pos.Right (cbAutoHideVerticalScrollBar) + 1, + Y = Pos.Bottom (labelContentSize), + CanFocus = false, + CheckedState = view.HorizontalScrollBar.AutoHide ? CheckState.Checked : CheckState.UnChecked + }; + view.HorizontalScrollBar.AutoHide = true; + cbAutoHideHorizontalScrollBar.CheckedStateChanging += AutoHideHorizontalScrollBar_Toggle; - void AutoHideScrollBars_Toggle (object sender, CancelEventArgs e) + void AutoHideHorizontalScrollBar_Toggle (object sender, CancelEventArgs e) { - view.HorizontalScrollBar.AutoHide = view.VerticalScrollBar.AutoHide = e.NewValue == CheckState.Checked; + view.HorizontalScrollBar.AutoHide = e.NewValue == CheckState.Checked; + } + + cbAllowNegativeX.CheckedStateChanging += AllowNegativeX_Toggle; + cbAllowNegativeY.CheckedStateChanging += AllowNegativeY_Toggle; + + cbAllowXGreaterThanContentWidth.CheckedStateChanging += AllowXGreaterThanContentWidth_Toggle; + cbAllowYGreaterThanContentHeight.CheckedStateChanging += AllowYGreaterThanContentHeight_Toggle; + + void SetHorizontalScrollBar (CheckState newValue, CheckState value) + { + if (newValue == CheckState.Checked) + { + cbAutoHideHorizontalScrollBar.CheckedState = CheckState.UnChecked; + view.HorizontalScrollBar.AutoHide = view.HorizontalScrollBar.ShowScrollIndicator = false; + cbHorizontalScrollBar.CheckedState = CheckState.UnChecked; + } + else + { + cbAutoHideHorizontalScrollBar.CheckedState = CheckState.Checked; + + view.HorizontalScrollBar.AutoHide = view.HorizontalScrollBar.ShowScrollIndicator = newValue == CheckState.UnChecked + && value == CheckState.UnChecked; + cbHorizontalScrollBar.CheckedState = CheckState.Checked; + } + } + + void SetVerticalScrollBar (CheckState newValue, CheckState value) + { + if (newValue == CheckState.Checked) + { + cbAutoHideVerticalScrollBar.CheckedState = CheckState.UnChecked; + view.VerticalScrollBar.AutoHide = view.VerticalScrollBar.ShowScrollIndicator = false; + cbVerticalScrollBar.CheckedState = CheckState.UnChecked; + } + else + { + cbAutoHideVerticalScrollBar.CheckedState = CheckState.Checked; + + view.VerticalScrollBar.AutoHide = view.VerticalScrollBar.ShowScrollIndicator = newValue == CheckState.UnChecked + && value == CheckState.UnChecked; + cbVerticalScrollBar.CheckedState = CheckState.Checked; + } } - view.Padding.Add (labelContentSize, contentSizeWidth, labelComma, contentSizeHeight, cbClearOnlyVisible, cbDoNotClipContent, cbVerticalScrollBar, cbHorizontalScrollBar, cbAutoHideScrollBars); + view.Padding.Add (labelContentSize, contentSizeWidth, labelComma, contentSizeHeight, cbClearOnlyVisible, cbDoNotClipContent, cbVerticalScrollBar, cbHorizontalScrollBar, cbAutoHideVerticalScrollBar, cbAutoHideHorizontalScrollBar); // Add demo views to show that things work correctly var textField = new TextField { X = 20, Y = 7, Width = 15, Text = "Test TextField" }; From 03dac3e238871c08e011666ebb3eed54afa6b6fc Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 7 Sep 2024 10:29:56 -0600 Subject: [PATCH 076/128] Merged bdisp latest. Updated CharMap. --- Terminal.Gui/View/ViewportSettings.cs | 21 ++++++++++++++++++++- UICatalog/Scenarios/CharacterMap.cs | 12 ++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/View/ViewportSettings.cs b/Terminal.Gui/View/ViewportSettings.cs index f0e2af75eb..711c876056 100644 --- a/Terminal.Gui/View/ViewportSettings.cs +++ b/Terminal.Gui/View/ViewportSettings.cs @@ -111,5 +111,24 @@ public enum ViewportSettings /// must be set for this setting to work (clipping beyond the visible area must be /// disabled). /// - ClearContentOnly = 32 + ClearContentOnly = 32, + + /// + /// If set, the vertical scroll bar (see ) will be enabled and automatically made visible + /// when the dimension of the is smaller than the dimension of . + /// + EnableHorizontalScrollBar = 64, + + /// + /// If set, the vertical scroll bar (see ) will be enabled and automatically made visible + /// when the dimension of the is smaller than the dimension of . + /// + EnableVerticalScrollBar = 128, + + /// + /// If set, the horizontal and vertical scroll bars (see cref="View.HorizontalScrollBar"/> and ) + /// will be enabled and automatically made visible when the dimension of the is smaller than the + /// dimension of . + /// + EnableScrollBars = EnableHorizontalScrollBar | EnableVerticalScrollBar } \ No newline at end of file diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 7ce7530950..c88d9f777c 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -464,10 +464,10 @@ public CharMap () ScrollBar hScrollBar = new () { - X = 0, + X = RowLabelWidth + 1, Y = Pos.AnchorEnd (), Width = Dim.Fill (1), - Size = GetContentSize ().Width, + Size = COLUMN_WIDTH * 15, Orientation = Orientation.Horizontal }; @@ -476,10 +476,10 @@ public CharMap () ScrollBar vScrollBar = new () { X = Pos.AnchorEnd (), - Y = 0, + Y = 1, // Header Height = Dim.Fill (Dim.Func (() => hScrollBar.Visible ? 1 : 0)), Orientation = Orientation.Vertical, - Size = GetContentSize ().Height + Size = GetContentSize ().Height - _rowHeight, // Minus one row so last row stays visible }; vScrollBar.PositionChanged += (sender, args) => { Viewport = Viewport with { Y = args.CurrentValue }; }; @@ -488,7 +488,7 @@ public CharMap () ViewportChanged += (sender, args) => { - vScrollBar.Size = GetContentSize ().Height; + vScrollBar.Size = GetContentSize ().Height - _rowHeight; // Minus one row so last row stays visible vScrollBar.Position = Viewport.Y; }; } @@ -970,7 +970,7 @@ private void ShowDetails () document.RootElement, new JsonSerializerOptions - { WriteIndented = true } + { WriteIndented = true } ); } From 38c604eefa41376678df21de737327cbf7f2062e Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 8 Sep 2024 17:26:25 -0600 Subject: [PATCH 077/128] More CharMap tweaks --- UICatalog/Scenarios/CharacterMap.cs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index c88d9f777c..a4b8fd9b27 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -320,7 +320,7 @@ public CharMap () CanFocus = true; CursorVisibility = CursorVisibility.Default; - SetContentSize (new (RowWidth, (_maxCodePoint / 16 + 2) * _rowHeight)); + SetContentSize (new (RowWidth, (_maxCodePoint / 16 + 1) * _rowHeight)); AddCommand ( Command.ScrollUp, @@ -369,6 +369,11 @@ public CharMap () ScrollHorizontal (-COLUMN_WIDTH); } + if (Cursor.X >= Viewport.Width) + { + ScrollHorizontal (Cursor.X - Viewport.Width + 1); + } + return true; } ); @@ -464,6 +469,7 @@ public CharMap () ScrollBar hScrollBar = new () { + AutoHide = false, X = RowLabelWidth + 1, Y = Pos.AnchorEnd (), Width = Dim.Fill (1), @@ -471,15 +477,14 @@ public CharMap () Orientation = Orientation.Horizontal }; - hScrollBar.VisibleChanged += (sender, args) => { Padding.Thickness = Padding.Thickness with { Bottom = hScrollBar.Visible ? 1 : 0 }; }; - ScrollBar vScrollBar = new () { + AutoHide = false, X = Pos.AnchorEnd (), Y = 1, // Header - Height = Dim.Fill (Dim.Func (() => hScrollBar.Visible ? 1 : 0)), + Height = Dim.Fill (Dim.Func (() => Padding.Thickness.Bottom)), Orientation = Orientation.Vertical, - Size = GetContentSize ().Height - _rowHeight, // Minus one row so last row stays visible + Size = GetContentSize ().Height, }; vScrollBar.PositionChanged += (sender, args) => { Viewport = Viewport with { Y = args.CurrentValue }; }; @@ -488,7 +493,19 @@ public CharMap () ViewportChanged += (sender, args) => { - vScrollBar.Size = GetContentSize ().Height - _rowHeight; // Minus one row so last row stays visible + if (Viewport.Width < GetContentSize ().Width) + { + Padding.Thickness = Padding.Thickness with { Bottom = 1 }; + } + else + { + Padding.Thickness = Padding.Thickness with { Bottom = 0 }; + } + + hScrollBar.Size = COLUMN_WIDTH * 15; + hScrollBar.Position = Viewport.X; + + vScrollBar.Size = GetContentSize ().Height; vScrollBar.Position = Viewport.Y; }; } From f4931c22346c760c9e6664a7a30238cac07fe8cd Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 9 Sep 2024 11:46:18 -0600 Subject: [PATCH 078/128] API doc and overview improvements --- Terminal.Gui/View/ViewportSettings.cs | 28 +--- Terminal.Gui/Views/Scroll/Scroll.cs | 7 +- Terminal.Gui/Views/Scroll/ScrollBar.cs | 7 +- docfx/docs/View.md | 205 ++++--------------------- docfx/docs/index.md | 2 +- docfx/docs/layout.md | 67 ++++++-- docfx/docs/scrolling.md | 50 ++++++ 7 files changed, 151 insertions(+), 215 deletions(-) create mode 100644 docfx/docs/scrolling.md diff --git a/Terminal.Gui/View/ViewportSettings.cs b/Terminal.Gui/View/ViewportSettings.cs index 711c876056..5bfb14ddea 100644 --- a/Terminal.Gui/View/ViewportSettings.cs +++ b/Terminal.Gui/View/ViewportSettings.cs @@ -15,43 +15,35 @@ public enum ViewportSettings /// If set, .X can be set to negative values enabling scrolling beyond the left of /// the /// content area. - /// - /// /// /// When not set, .X is constrained to positive values. /// - /// + /// AllowNegativeX = 1, /// /// If set, .Y can be set to negative values enabling scrolling beyond the top of the /// content area. - /// - /// /// /// When not set, .Y is constrained to positive values. /// - /// + /// AllowNegativeY = 2, /// /// If set, .Size can be set to negative coordinates enabling scrolling beyond the /// top-left of the /// content area. - /// - /// /// /// When not set, .Size is constrained to positive coordinates. /// - /// + /// AllowNegativeLocation = AllowNegativeX | AllowNegativeY, /// /// If set, .X can be set values greater than /// .Width enabling scrolling beyond the right /// of the content area. - /// - /// /// /// When not set, .X is constrained to /// .Width - 1. @@ -61,15 +53,13 @@ public enum ViewportSettings /// /// The practical effect of this is that the last column of the content will always be visible. /// - /// + /// AllowXGreaterThanContentWidth = 4, /// /// If set, .Y can be set values greater than /// .Height enabling scrolling beyond the right /// of the content area. - /// - /// /// /// When not set, .Y is constrained to /// .Height - 1. @@ -79,21 +69,19 @@ public enum ViewportSettings /// /// The practical effect of this is that the last row of the content will always be visible. /// - /// + /// AllowYGreaterThanContentHeight = 8, /// /// If set, .Size can be set values greater than /// enabling scrolling beyond the bottom-right /// of the content area. - /// - /// /// /// When not set, is constrained to -1. /// This means the last column and row of the content will remain visible even if there is an attempt to /// scroll the Viewport past the last column or row. /// - /// + /// AllowLocationGreaterThanContentSize = AllowXGreaterThanContentWidth | AllowYGreaterThanContentHeight, /// @@ -106,11 +94,9 @@ public enum ViewportSettings /// If set will clear only the portion of the content /// area that is visible within the . This is useful for views that have a /// content area larger than the Viewport and want the area outside the content to be visually distinct. - /// - /// /// must be set for this setting to work (clipping beyond the visible area must be /// disabled). - /// + /// ClearContentOnly = 32, /// diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index e97650bf23..9240c04c03 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -5,8 +5,11 @@ namespace Terminal.Gui; /// -/// Indicates the position and size of scrollable content. The indicator can be dragged with the mouse. Can be -/// oriented either vertically or horizontally. Used within a . +/// 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 +/// . /// /// /// diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index d9916e332e..e3855f53bb 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -4,7 +4,12 @@ namespace Terminal.Gui; -/// A proportional scroll bar that can be oriented either horizontally or vertically. +/// +/// Provides a visual indicator that content can be scrolled. ScrollBars consist of two buttons, one each for scrolling +/// forward or backwards, a Scroll that can be clicked to scroll large amounts, and a ScrollSlider that can be dragged +/// to scroll continuously. ScrollBars can be oriented either horizontally or vertically and support the user dragging +/// and clicking with the mouse to scroll. +/// /// /// /// indicates the current location between zero and . diff --git a/docfx/docs/View.md b/docfx/docs/View.md index 9677ad2ccc..f0e34913a0 100644 --- a/docfx/docs/View.md +++ b/docfx/docs/View.md @@ -1,192 +1,39 @@ -# V2 Spec for View Refactor - WORK IN PROGRESS +# View Deep Dive -IMPORTANT: I am critical of the existing codebase below. Do not take any of this personally. It is about the code, not the amazing people who wrote the code. +## View Lexicon & Taxonomy -ALSO IMPORTANT: I've written this to encourage and drive DEBATE. My style is to "Have strong opinions, weakly held." If you read something here you don't understand or don't agree with, SAY SO. Tell me why. Take a stand. +### Hierarchy -This covers my thinking on how we will refactor `View` and the classes in the `View` hierarchy (including `Responder`). It does not cover Text formatting which will be covered in another spec. - * TrueColor support will be covered separately. - * ConsoleDriver refactor. + * *[View](~/api/Terminal.Gui.View.yml)* - The base class for implementing higher-level visual/interactive Terminal.Gui elements. Implemented in the [View](~/api/Terminal.Gui.View.yml) base class. + + * *SubView* - A View that is contained in another view and will be rendered as part of the containing view's *ContentArea*. SubViews are added to another view via the [View.Add](~/api/Terminal.Gui.View.Add.yml) method. A View may only be a SubView of a single View. Each View has a [Subviews](~/api/Terminal.Gui.View.Subviews.yml) property that is a list of all Subviews that have been added. + + * *[SuperView](~/api/Terminal.Gui.View.SuperView.yml)* - The View that is a container for SubViews. Each View has a [SuperView](~/api/Terminal.Gui.View.SuperView.yml) property that references it's SuperView after it has been added. + + * *Child View* - A view that holds a reference to another view in a parent/child relationship. Terminal.Gui uses the terms "Child" and "Parent" sparingly. Generally Subview/SuperView is preferred. + + * *Parent View* - A view that holds a reference to another view in a parent/child relationship, but is NOT a SuperView of the child. Terminal.Gui uses the terms "Child" and "Parent" sparingly. Generally Subview/SuperView is preferred. + +### Layout -## Goals +See the [Layout Deep Dive](layout.md). -1. Refactor View to have "real" Bounds where the Location part can be non-zero -2. Enable a real "margin", "border", and "padding" thickness can be implemented that matches how these concepts work in HTML -3. Leverage LineCanvas to draw borders and auto-join borders. Remove the need for `TileVeiw` and `SplitView` classes. -4. Reduce 20/30% of the existing View, Toplevel, Window, and FrameView can code. -5. Make porting apps to use the new architecture relatively easy, but result in less code in apps. -6. Make it easier to add new Views and View-like classes. +### Drawing -## Terminal.Gui v2 View-related Lexicon & Taxonomy +See the [Drawing Deep Dive](drawing.md). - * *Responder* - A class that can handle user input. Implemented in the `Responder` base class. - * In v2 we will move more mouse/keyboard base-logic out of `View` and `Window` and into `Responder`. - * *View* - A base class for implementing higher-level visual/interactive Terminal.Gui elements. Implemented in the `View` base class, which is a `Responder` and hosts several `Frame`s. - * In v2 we will move all logic for rendering out of `Toplevel`, `FrameView`, and `Window` into `View`. - * *SubView* - A View that is contained in another view and will be rendered as part of the containing view's *ContentArea*. SubViews are added to another view via the `View.Add` method. A View may only be a SubView of a single View. - * *SuperView* - The View that is a container for SubViews. Referring to the View another View was added to as *SubView*. - * *Child View* - A view that is held by another view in a parent/child relationship, but is NOT a SubView. Examples of this are the submenus of `MenuBar`. - * *Parent View* - A view that holds a reference to another view in a parent/child relationship, but is NOT a SuperView of the child. - * *Thickness* - A class describing a rectangle where each of the four sides can have a width. Valid width values are >= 0. The inner area of a Thickness is the sum of the widths of the four sides minus the size of the rectangle. The `Thickness` class has a `Draw` method that clears the rectangle. - * *Frame Class* - A `Frame` is a special form of `View` that appears outside of a normal `View`'s content area. Examples of `Frame`s are `Margin`, `Border`, and `Padding`. The `Frame` class is derived from `View` and uses a `Thickness` to hold the rectangle. - * *Frame* - The `Rect` that defines the location and size of the `View` including all of the margin, border, adornments, padding, and content area. The coordinates are relative to the SuperView of the View (or, in the case of `Application.Top`, `ConsoleDriver.Row == 0; ConsoleDriver.Col == 0`). The Frame's location and size are controlled by the `.X`, `.Y`, `.Height`, and `.Width` properties of the View. - * In v2, `View.Frame.Size` is the size of the `View`'s `ContentArea` plus the `Thickness` of the `View`'s `Margin`, `Border`, and `Padding`. - * *Margin* - The `Frame` that separates a View from other SubViews of the same SuperView. The Margin is not part of the View's content and is not clipped by the View's `ClipArea`. By default `Margin` is `{0,0,0,0}`. `Margin` can be used instead of (or with) `Dim.Pos` to position a View relative to another View. - Eg. - ```cs - view.X = Pos.Right (otherView) + 1; - view.Y = Pos.Bottom (otherView) + 1; - ``` - is equivalent to - ```cs - otherView.Margin.Thickness = new Thickness (0, 0, 1, 1); - view.X = Pos.Right (otherView); - view.Y = Pos.Bottom (otherView); - ``` - * QUESTION: Will it be possible to have a negative Margin? If so, will that allow us to have "magic borderframe connections" as I've demonstrated in my TileViewExperiment? Or, should the magic happen when a View's dimensions overlap with another, independent of the Margin? - * *Title* - Text that is displayed for the View that describes the View to users. Typically the Title is displayed at the top-left, overlaying the Border. The title is not part of the View's content and is not clipped by the View's `ClipArea`. - * *Text* - Text that is rendered by the view within the view's content area, using `TextFormatter`. `Text` is part of the View's content and is clipped by the View's `ClipArea`. - * *Border* (currently `BorderFrame` until the old `Border` can be removed) - The `Frame` where a visual border (drawn using line-drawing glyphs) and the Title are drawn. The Border expands inward; in other words if `Border.Thickness.Top == 2` the border & title will take up the first row and the second row will be filled with spaces. The Border is not part of the View's content and is not clipped by the View's `ClipArea`. - * *Adornments* (NOT IMPLEMENTED YET; May replace `BorderFrame`)- The `Frame` between the `Border` and `Padding`. Adornments are not part of the View's content and are not clipped by the View's `ClipArea`. Examples of Adornments: - * A `TitleBar` renders the View's `Title` and a horizontal line defining the top of the View. Adds thickness to the top of Adornments. - * One or more `LineView`s that render the View's border (NOTE: The magic of `LineCanvas` lets us automatically have the right joins for these and `TitleBar`!). - * A `Vertical Scrollbar` adds thickness to `Adornments.Right` (or `.Left` when right-to-left language support is added). - * A `Horizontal Scrollbar` adds thickness to `Adornments.Bottom` when enabled. - * A `MenuBar` adds thickness to `Adornments.Top` (NOTE: This is a change from v1 where `subview.Y = 1` is required). - * A `StatusBar` adds thickness ot `Adornments.Bottom` and is rendered at the bottom of `Padding`. - * NOTE: The use of `View.Add` in v1 to add adornments to Views is the cause of much code complexity. Changing the API such that `View.Add` is ONLY for subviews and adding a `View.Adornments.Add` API for menu, StatusBar, scroll bar... will enable us to significantly simplify the codebase. - * *Padding* - The `Frame` inside of an element that offsets the `Content` from the Border. (NOTE: in v1 `Padding` is OUTSIDE of the `Border`). Padding is `{0, 0, 0, 0}` by default. Padding is not part of the View's content and is not clipped by the View's `ClipArea`. - * *VisibleArea* - (NOT IMPLEMENTED YET) Means the area inside of the Margin + Border (Title) + Padding. `VisibleArea.Location` is always `{0, 0}`. `VisibleArea.Size` is the `View.Frame.Size` shrunk by Margin + Border + Padding. - * *ContentArea* - (NOT IMPLEMENTED YET; currently `Bounds`) The `Rect` that describes the location and size of the View's content, relative to `VisibleArea`. If `ContentArea.Location` is negative, anything drawn there will be clipped and any subview positioned in the negative area will cause (optional) scrollbars to appear (making the Thickness of Padding thicker on the appropriate sides). If `ContentArea.Size` is changed such that the dimensions fall outside of `Frame.Size shrunk by Margin + Border + `Padding`, drawing will be clipped and (optional) scrollbars will appear. - * QUESTION: Can we just have one `ContentArea` property that is the `Rect` that describes the location and size of the View's content, relative to `Frame`? If so, we can remove `VisibleArea` and `Bounds` and just have `ContentArea` and `Frame`? The key to answering this is all wrapped up in scrolling and clipping. - * *Bounds* - Synomous with *VisibleArea*. (Debate: Do we rename `Bounds` to `VisbleArea` in v2?) - * *ClipArea* - The currently visible portion of the *Content*. This is defined as a`Rect` in coordinates relative to *ContentArea* (NOT *VisibleArea*) (e.g. `ClipArea {X = 0, Y = 0} == ContentArea {X = 0, Y = 0}`). In v2 we will NOT pass this `Rect` is passed `View.Redraw` and instead just have `Redraw` use `Bounds`. - * QUESTION: Do we need `ClipArea` at all? Can we just have `Redraw` use `Bounds`? +### Navigation - * *Modal* - *Modal* - The term used when describing a `View` that was created using the `Application.Run(view)` or `Application.Run` APIs. When a View is running as a modal, user input is restricted to just that View until `Application.Run` exits. A `Modal` View has its own `RunState`. - * In v1, classes derived from `Dialog` were originally thought to only work modally. However, `Wizard` proved that a `Dialog`-based class can also work non-modally. - * In v2, we will simplify the `Dialog` class, and let any class be run via `Applicaiton.Run`. The `Modal` property will be set by `Application.Run` so the class can detect it is running modally if it needs to. - - * *TopLevel* - The v1 term used to describe a view that can have a MenuBar and/or StatusBar. In v2, we will delete the `TopLevel` class and ensure ANY View can have a menu bar and/or status bar (via `Adornments`). - * NOTE: There will still be an `Application.Top` which is the `View` that is the root of the `Application`'s view hierarchy. - - * *Window* - A View that, by default, has a `Border` and a `Title`. - * QUESTION: Why can't this just be a property on `View` (e.g. `View.Border = true`)? Why do we need a `Window` class at all in v2? - - * *Tile*, *Tiled*, *Tiling* (NOT IMPLEMENTED YET) - Refer to a form of `ComputedLayout` where SubViews of a `View` are visually arranged such that they abut each other and do not overlap. In a Tiled view arrangement, Z-ordering only comes into play when a developer intentionally causes views to be aligned such that they overlap. Borders that are drawn between the SubViews can optionally support resizing the SubViews (negating the need for `TileView`). - - * *Overlap*, *Overlapped*, *Overlapping* (NOT IMPLEMENTED YET) - Refers to a form of `ComputedLayout` where SubViews of a View are visually arranged such that their Frames overlap. In Overlap view arrangements there is a Z-axis (Z-order) in addition to the X and Y dimension. The Z-order indicates which Views are shown above other views. - -## Focus - -* Focus is a concept that is used to describe which Responder is currently receiving user input. -* QUESTION: Since `Frame`s are `Views` in v2, the `Frame` is a `Responder` that receives user input. This raises the question of how a user can use the keyboard to navigate between `Frame`s and `View`s within a `Frame` (and the `Frame`'s `Parent`'s subviews). - - -## View classes to be nuked -* PanelView (done) -* FrameView (almost done) -* TileVeiw -* TopLevel? -* Window? -* `LineView` can be reimplemented using `LineCanvas`? -* `Button` and `Label` can be merged. -* `StatusBar` and `MenuBar` could be combined. If not, then at least made consistent (e.g. in how hotkeys are specified). -* `ComboBox` can be replaced by `MenuBar` +See the [Navigation Deep Dive](navigation.md). -## What's wrong with the View and the View-class hierarchy in v1? +### Application Concepts -* `Frame`, `Bounds`, and `ClipRect` are confusing and not consistently applied... - * `Bounds` is `Rect` but is used to describe a `Size` (e.g. `Bounds.Size` is the size of the `View`'s content area). It literally is implemented as a property that returns `new Rect(0, 0, Width, Height)`. Throughtout the codebase `bounds` is used for things that have non-zero `Size` (and actually descibe either the cliprect or the Frame). - * The restrictive nature of how `Bounds` is defined led to the hacky `FrameView` and `Window` classes with an embedded `ContentView` in order to draw a border around the content. - * The only reason FrameView exists is because the original architecture didn't support offsetting `View.Bounds` such that a border could be drawn and the interior content would clip correctly. Thus Miguel (or someone) built - FrameView with nested `ContentView` that was at `new Rect(+1, +1, -2, -2)`. - * `Border` was added later, but couldn't be retrofitted into `View` such that if `View.Border ~= null` just worked like `FrameView`. - * Thus devs are forced to use the clunky `FrameView` instead of just setting `View.Border`. - * `Border` has a bunch of confusing concepts that don't match other systems (esp the Web/HTML) - * `Margin` on the web means the space between elements - `Border` doesn't have a margin property, but does has the confusing `DrawMarginFrame` property. - * `Border` on the web means the space where a border is drawn. The current implementaiton confuses the term `Frame` and `Border`. `BorderThickness` is provided. - * `Padding` on the web means the padding inside of an element between the `Border` and `Content`. In the current implementation `Padding` is actually OUTSIDE of the `Border`. This means it's not possible for a view to offset internally by simply changing `Bounds`. - * `Content` on the web means the area inside of the Margin + Border + Padding. `View` does not currently have a concept of this (but `FrameView` and `Window` do via the embedded `ContentView`s. - * `Border` has a `Title` property. So does `Window` and `FrameView`. This is duplicate code. - * It is not possible for a class derived from View to override the drawing of the "Border" (frame, title, padding, etc...). Multiple devs have asked to be able to have the border frame to be drawn with a different color than `View.ColorScheme`. The API should explicitly enable devs to override the drawing of `Border` independently of the `View.Draw` method. See how `WM_NCDRAW` works in Windows (Draw non-client). It should be easy to do this from within a `View` sub-class (e.g. override `OnDrawBorder`) and externally (e.g. `DrawBorder += () => ...`. - -* `AutoSize` mostly works, but only because of heroic special-casing logic all over the place by @bdisp. This should be massively simplified.`FrameView` is superfluous and should be removed from the hierarchy (instead devs should just be able to manipulate `View.Border` (or similar) to achieve what `FrameView` provides). The internal `FrameView.ContentView` is a bug-farm and un-needed if `View.Border` worked correctly. -* `TopLevel` is currently built around several concepts that are muddled: - * Views that host a Menu and StatusBar. It is not clear why this is and if it's needed as a concept. - * Views that can be run via `Application.Run` (need a separate `RunState`). It is not clear why ANY VIEW can't be run this way, but it seems to be a limitation of the current implementation. - * Views that can be used as a pop-up (modal) (e.g. `Dialog`). As proven by `Wizard`, it is possible to build a View that works well both ways. But it's way too hard to do this today. - * Views that can be moved by the user must inherit from `Window` today. It should be possilbe to enable moving of any View (e.g. `View.CanMove = true`). -* The `MdiContainer` stuff is complex, perhaps overly so, and is not actually used by anyone outside of the project. It's also mis-named because Terminal.Gui doesn't actually support "documents" nor does it have a full "MDI" system like Windows (did). It seems to represent features useful in overlapping Views, but it is super confusing on how this works, and the naming doesn't help. This all can be refactored to support specific scenarios and thus be simplified. -* There is no facility for users' resizing of Views. @tznind's awesome work on `LineCanvas` and `TileView` combined with @tig's experiments show it could be done in a great way for both modal (overlapping) and tiled Views. -* `DrawFrame` and `DrawTitle` are implemented in `ConsoleDriver` and can be replaced by a combination of `LineCanvas` and `Border`. -* Colors - - * As noted above each of Margin, Border, Padding, and Content should support independent colors. - * Many View sub-classes bastardize the existing ColorSchemes to get look/feel that works (e.g. `TextView` and `Wizard`). Separately we should revamp ColorSchemes to enable more scenarios. - * TrueColor support is needed and should be the default. -* `Responder` is supposed to be where all common, non-visual-related, code goes. We should ensure this is the case. -* `View` should have default support for scroll bars. e.g. assume in the new world `View.ContentBounds` is the clip area (defined by `VIew.Frame` minus `Margin` + `Border` + `Padding`) then if any view is added with `View.Add` that has Frame coordinates outside of `ContentBounds` the appropriate scroll bars show up automatgically (optionally of course). Without any code, scrolling just works. -* We have many requests to support non-full-screen apps. We need to ensure the `View` class hierarchy supports this in a simple, understandable way. In a world with non-full-screen (where screen is defined as the visible terminal view) apps, the idea that `Frame` is "screen relative" is broken. Although we COULD just define "screen" as "the area that bounds the Terminal.GUI app.". - - -## Design - -* `Responder`("Responder base class implemented by objects that want to participate on keyboard and mouse input.") remains mostly unchanged, with minor changes: - * Methods that take `View` parameters change to take `Responder` (bad OO design). - * Nuke `IsOverriden` (bad OO design) - * Move `View.Data` to `Responder` (primitive) - * Move `Command` and `KeyBinding` stuff from `View`. - * Move the generic mouse and keyboard stuff from `View` (e.g. `WantMousePositionReports`) - - -## Example of creating Adornments -```cs -// ends up looking just like the v1 default Window with a menu & status bar -// and a vertical scrollbar. In v2 the Window class would do all of this automatically. -var top = new TitleBar() { - X = 0, Y = 0, - Width = Dim.Fill(), - Height = 1 - LineStyle = LineStyle.Single -}; -var left = new LineView() { - X = 0, Y = 0, - Width = 1, - Height = Dim.Fill(), - LineStyle = LineStyle.Single -}; -var right = new LineView() { - X = Pos.AnchorEnd(), Y = 0, - Width = 1, - Height = Dim.Fill(), - LineStyle = LineStyle.Single -}; -var bottom = new LineView() { - X = 0, Y = Pos.AnchorEnd(), - Width = Dim.Fill(), - Height = 1, - LineStyle = LineStyle.Single -}; + * *TopLevel* - The v1 term used to describe a view that can have a MenuBar and/or StatusBar. In v2, we will delete the `TopLevel` class and ensure ANY View can have a menu bar and/or status bar (via `Adornments`). + * NOTE: There will still be an `Application.Top` which is the [View](~/api/Terminal.Gui.View.yml) that is the root of the `Application`'s view hierarchy. -var menu = new MenuBar() { - X = Pos.Right(left), Y = Pos.Bottom(top) -}; -var status = new StatusBar () { - X = Pos.Right(left), Y = Pos.Top(bottom) -}; -var vscroll = new ScrollBarView () { - X = Pos.Left(right), - Y = Dim.Fill(2) // for menu & status bar -}; + * *Runnable* - TBD -Adornments.Add(titleBar); -Adornments.Add(left); -Adornments.Add(right); -Adornments.Add(bottom); -Adornments.Add(vscroll); + * *Modal* - *Modal* - The term used when describing a [View](~/api/Terminal.Gui.View.yml) that was created using the `Application.Run(view)` or `Application.Run` APIs. When a View is running as a modal, user input is restricted to just that View until `Application.Run` exits. A `Modal` View has its own `RunState`. + * In v1, classes derived from `Dialog` were originally thought to only work modally. However, `Wizard` proved that a `Dialog`-based class can also work non-modally. + * In v2, we will simplify the `Dialog` class, and let any class be run via `Applicaiton.Run`. The `Modal` property will be set by `Application.Run` so the class can detect it is running modally if it needs to. -var treeView = new TreeView () { - X = 0, Y = 0, Width = Dim.Fill(), Height = Dim.Fill() -}; -Add (treeView); -``` diff --git a/docfx/docs/index.md b/docfx/docs/index.md index 565dc54089..9fd738e944 100644 --- a/docfx/docs/index.md +++ b/docfx/docs/index.md @@ -110,7 +110,7 @@ All visible elements in a Terminal.Gui application are implemented as See the full list of [Views provided by the Terminal.Gui library here](views.md). Every view can contain an arbitrary number of child views, called `SubViews`. Call the -[View.Add](~/api/Terminal.Gui.View.yml#Terminal_Gui_View_Add_Terminal_Gui_View_) method to add a couple of buttons to a UI: +[View.Add](~/api/Terminal.Gui.View.Add.yml) method to add a couple of buttons to a UI: ```csharp void SetupMyView (View myView) diff --git a/docfx/docs/layout.md b/docfx/docs/layout.md index 639d2d5c94..d13980647b 100644 --- a/docfx/docs/layout.md +++ b/docfx/docs/layout.md @@ -1,28 +1,67 @@ # Layout -Terminal.Gui provides a rich system for how `View` objects are laid out relative to each other. The layout system also defines how coordinates are specified. +Terminal.Gui provides a rich system for how [View](View.md) objects are laid out relative to each other. The layout system also defines how coordinates are specified. -## Coordinates +See [View Deep Dive](View.md) for more. -* **Screen-Relative** - Describes the dimensions and characteristics of the underlying terminal. Currently Terminal.Gui only supports applications that run "full-screen", meaning they fill the entire terminal when running. As the user resizes their terminal, the `Screen` changes size and the applicaiton will be resized to fit. *Screen-Relative* means an origin (`0, 0`) at the top-left corner of the terminal. `ConsoleDriver`s operate exclusively on *Screen-Relative* coordinates. -* **Application-Relative** - The dimensions and characteristics of the application. Because only full-screen apps are currently supported, `Application` is effectively the same as `Screen` from a layout perspective. *Application-Relative* currently means an origin (`0, 0`) at the top-left corner of the terminal. `Applicaiton.Top` is a `View` with a top-left corner fixed at the *Application.Relative* coordinate of (`0, 0`) and is the size of `Screen`. -* **Frame-Relative** - The `Frame` property of a `View` is a rectangle that describes the current location and size of the view relative to the `Superview`'s content area. *Frame-Relative* means a coordinate is relative to the top-left corner of the View in question. `View.FrameToScreen ()` and `View.ScreenToFrame ()` are helper methods for translating a *Frame-Relative* coordinate to a *Screen-Relative* coordinate and vice-versa. -* **Content-Relative** - A rectangle, with an origin of (`0, 0`) and size (defined by `View.GetContentSize()`) where the View's content exists. *Content-Relative* means a coordinate is relative to the top-left corner of the content, which is always (`0,0`). `View.ContentToScreen ()` and `View.ScreenToContent ()` are helper methods for translating a *Content-Relative* coordinate to a *Screen-Relative* coordinate and vice-versa. -* **Viewport-Relative** - A *Content-Relative* rectangle representing the subset of the View's content that is visible to the user. If `View.GetContentSize()` is larger than the Viewport, scrolling is enabled. *Viewport-Relative* means a coordinate that is bound by (`0,0`) and the size of the inner-rectangle of the View's `Padding`. The View drawing primitives (e.g. `View.Move`) take *Viewport-Relative* coordinates; `Move (0, 0)` means the `Cell` in the top-left corner of the inner rectangle of `Padding`. `View.ViewportToScreen ()` and `View.ScreenToViewport ()` are helper methods for translating a *Viewport-Relative* coordinate to a *Screen-Relative* coordinate and vice-versa. To convert a *Viewport-Relative* coordinate to a *Content-Relative* coordinate, simply subtract `Viewport.X` and/or `Viewport.Y` from the *Content-Relative* coordinate. To convert a *Viewport-Relative* coordinate to a *Frame-Relative* coordinate, subtract the point returned by `View.GetViewportOffsetFromFrame`. +## Lexicon & Taxonomy + +### Coordinates + +* **Screen-Relative** - Describes the dimensions and characteristics of the underlying terminal. Currently Terminal.Gui only supports applications that run "full-screen", meaning they fill the entire terminal when running. As the user resizes their terminal, the [Screen](~/api/Terminal.Gui.Application.Screen.yml) changes size and the application will be resized to fit. *Screen-Relative* means an origin (`0, 0`) at the top-left corner of the terminal. [ConsoleDrivers](~/api/Terminal.Gui.ConsoleDriver.yml) s operate exclusively on *Screen-Relative* coordinates. + +* **Application-Relative** - The dimensions and characteristics of the application. Because only full-screen apps are currently supported, [Application](~/api/Terminal.Gui.Application.yml) is effectively the same as `Screen` from a layout perspective. *Application-Relative* currently means an origin (`0, 0`) at the top-left corner of the terminal. [Application.Top](~/api/Terminal.Gui.Application.Top.yml) is a `View` with a top-left corner fixed at the *Application.Relative* coordinate of (`0, 0`) and is the size of `Screen`. + +* **Frame-Relative** - The [Frame](~/api/Terminal.Gui.View.Frame.yml) property of a `View` is a rectangle that describes the current location and size of the view relative to the `Superview`'s content area. *Frame-Relative* means a coordinate is relative to the top-left corner of the View in question. [View.FrameToScreen()](~/api/Terminal.Gui.View.FrameToScreen.yml) and [View.ScreenToFrame()](~/api/Terminal.Gui.View.ScreenToFrame.yml) are helper methods for translating a *Frame-Relative* coordinate to a *Screen-Relative* coordinate and vice-versa. + +* **Content-Relative** - A rectangle, with an origin of (`0, 0`) and size (defined by [View.GetContentSize()](~/api/Terminal.Gui.View.GetContentSize.yml)) where the View's content exists. *Content-Relative* means a coordinate is relative to the top-left corner of the content, which is always (`0,0`). [View.ContentToScreen()](~/api/Terminal.Gui.View.ContentToScreen.yml) and [View.ScreenToContent()](~/api/Terminal.Gui.View.ScreenToContent.yml) are helper methods for translating a *Content-Relative* coordinate to a *Screen-Relative* coordinate and vice-versa. + +* **Viewport-Relative** - A *Content-Relative* rectangle representing the subset of the View's content that is visible to the user: [Viewport](~/api/Terminal.Gui.View.Viewport.yml). If [View.GetContentSize()](~/api/Terminal.Gui.View.GetContentSize.yml) is larger than the [Viewport](~/api/Terminal.Gui.View.Viewport.yml), scrolling is enabled. *Viewport-Relative* means a coordinate that is bound by (`0,0`) and the size of the inner-rectangle of the View's `Padding`. The View drawing primitives (e.g. `View.Move`) take *Viewport-Relative* coordinates; `Move (0, 0)` means the `Cell` in the top-left corner of the inner rectangle of `Padding`. `View.ViewportToScreen ()` and `View.ScreenToViewport ()` are helper methods for translating a *Viewport-Relative* coordinate to a *Screen-Relative* coordinate and vice-versa. To convert a *Viewport-Relative* coordinate to a *Content-Relative* coordinate, simply subtract `Viewport.X` and/or `Viewport.Y` from the *Content-Relative* coordinate. To convert a *Viewport-Relative* coordinate to a *Frame-Relative* coordinate, subtract the point returned by [View.GetViewportOffsetFromFrame()](~/api/Terminal.Gui.View.GetViewportOffsetFromFrame.yml). + +### View Composition + +* *[Thickness](~/api/Terminal.Gui.Thickness.yml)* - A `record struct` describing a rectangle where each of the four sides can have a width. Valid width values are >= 0. The inner area of a Thickness is the sum of the widths of the four sides minus the size of the rectangle. + +* *[Frame](~/api/Terminal.Gui.View.Frame.yml)* - The `Rectangle` that defines the location and size of the [View](~/api/Terminal.Gui.View.yml) including all of the margin, border, padding, and content area. The coordinates are relative to the SuperView of the View (or, in the case of `Application.Top`, `ConsoleDriver.Row == 0; ConsoleDriver.Col == 0`). The Frame's location and size are controlled by the `.X`, `.Y`, `.Height`, and `.Width` properties of the View. + +* *Adornments* - The `Thickness`es that separate the `Frame` from the `ContentArea`. There are three Adornments, `Margin`, `Padding`, and `Border`. Adornments are not part of the View's content and are not clipped by the View's `ClipArea`. Examples of Adornments: + +* *[Margin](~/api/Terminal.Gui.View.Margin.yml)* - The `Adornment` that separates a View from other SubViews of the same SuperView. The Margin is not part of the View's content and is not clipped by the View's `ClipArea`. By default `Margin` is `{0,0,0,0}`. `Margin` can be used instead of (or with) `Dim.Pos` to position a View relative to another View. + Eg. + ```cs + view.X = Pos.Right (otherView) + 1; + view.Y = Pos.Bottom (otherView) + 1; + ``` + is equivalent to + ```cs + otherView.Margin.Thickness = new Thickness (0, 0, 1, 1); + view.X = Pos.Right (otherView); + view.Y = Pos.Bottom (otherView); + ``` + +* *[Border](~/api/Terminal.Gui.View.Border.yml)* - The `Adornment` where a visual border (drawn using line-drawing glyphs) and the [Title](~/api/Terminal.Gui.View.Title.yml) are drawn. The Border expands inward; in other words if `Border.Thickness.Top == 2` the border & title will take up the first row and the second row will be filled with spaces. The Border is not part of the View's content and is not clipped by the View's `Clip`. + +* *[Padding](~/api/Terminal.Gui.View.Padding.yml)* - The `Adornment` that offsets the `ContentArea` from the `Border`. `Padding` is `{0, 0, 0, 0}` by default. Padding is not part of the View's content and is not clipped by the View's `Clip`. When, enabled, scroll bars reside within `Padding`. + +## Layout Modes + +* *Tile*, *Tiled*, *Tiling* - Refer to a form of [Layout](layout.md) where SubViews of a [View](~/api/Terminal.Gui.View.yml) are visually arranged such that they abut each other and do not overlap. In a Tiled view arrangement, Z-ordering only comes into play when a developer intentionally causes views to be aligned such that they overlap. Borders that are drawn between the SubViews can optionally support resizing the SubViews (negating the need for `TileView`). + +* *Overlap*, *Overlapped*, *Overlapping* - Refers to a form [Layout](layout.md) where SubViews of a View are visually arranged such that their Frames overlap. In Overlap view arrangements there is a Z-axis (Z-order) in addition to the X and Y dimension. The Z-order indicates which Views are shown above other views. ## The Frame -The `Frame` property of a `View` is a rectangle that describes the current location and size of the view relative to the `Superview`'s content area. The `Frame` has a `Location` and `Size`. The `Location` describes the top-left corner of the view relative to the `Superview`'s content area. The `Size` describes the width and height of the view. The `Frame` is used to determine where the view is drawn on the screen and is used to calculate the Viewport and content size. +The [Frame](~/api/Terminal.Gui.View.Frame.yml) property of a `View` is a rectangle that describes the current location and size of the view relative to the `Superview`'s content area. The `Frame` has a `Location` and `Size`. The `Location` describes the top-left corner of the view relative to the `SuperView`'s content area. The `Size` describes the width and height of the view. The `Frame` is used to determine where the view is drawn on the screen and is used to calculate the Viewport and content size. ## The Content Area - The content area is the area where the view's content is drawn. Content can be any combination of the `View.Text` property, `Subviews`, and other content drawn by the View. The `View.GetContentSize()` property gets the size of the content area of the view. *Content Area* refers to the rectangle with a location of `0,0` with a size of `ContentSize`. + The content area is the area where the view's content is drawn. Content can be any combination of the [View.Text](~/api/Terminal.Gui.View.Text.yml) property, `Subviews`, and other content drawn by the View. The [View.GetContentSize()](~/api/Terminal.Gui.View.GetContentSize.yml) method gets the size of the content area of the view. *Content Area* refers to the rectangle with a location of `0,0` with the size returned by [View.GetContentSize()](~/api/Terminal.Gui.View.GetContentSize.yml). - The Content size tracks the size of the `Viewport` by default. If the content size is set via `SetContentSize()`, the content area is the provided size. If the content size is larger than the `Viewport`, scrolling is enabled. + The Content Area size tracks the size of the [Viewport](~/api/Terminal.Gui.View.Viewport.yml) by default. If the content size is set via [View.SetContentSize()](~/api/Terminal.Gui.View.SetContentSize.yml), the content area is the provided size. If the content size is larger than the [Viewport](~/api/Terminal.Gui.View.Viewport.yml), scrolling is enabled. ## The Viewport -The Viewport (`View.Viewport`) is a rectangle describing the portion of the *Content Area* that is currently visible to the user. It is a "portal" into the content. The `Viewport.Location` is relative to the top-left corner of the inner rectangle of `View.Padding`. If `Viewport.Size` is the same as `View.GetContentSize()`, `Viewport.Location` will be `0,0`. +The Viewport ([Viewport](~/api/Terminal.Gui.View.Viewport.yml)) is a rectangle describing the portion of the *Content Area* that is currently visible to the user. It is a "portal" into the content. The `Viewport.Location` is relative to the top-left corner of the inner rectangle of `View.Padding`. If `Viewport.Size` is the same as `View.GetContentSize()`, `Viewport.Location` will be `0,0`. To enable scrolling call `View.SetContentSize()` and then set `Viewport.Location` to positive values. Making `Viewport.Location` positive moves the Viewport down and to the right in the content. @@ -30,6 +69,12 @@ The `View.ViewportSettings` property controls how the Viewport is constrained. B The default `ViewportSettings` also constrains the Viewport to the size of the content, ensuring the right-most column or bottom-most row of the content will always be visible (in v1 the equivalent concept was `ScrollBarView.AlwaysKeepContentInViewport`). To allow the Viewport to be smaller than the content, set `ViewportSettings.AllowXGreaterThanContentWidth` and/or `ViewportSettings.AllowXGreaterThanContentHeight`. + + +* *[Content Area](~/api/Terminal.Gui.View.GetContentSize().yml)* - The content area is the area where the view's content is drawn. Content can be any combination of the [View.Text](~/api/Terminal.Gui.View.Text.yml) property, `Subviews`, and other content drawn by the View. The [View.GetContentSize()](~/api/Terminal.Gui.View.GetContentSize.yml) method gets the size of the content area of the view. *Content Area* refers to the rectangle with a location of `0,0` with the size returned by [View.GetContentSize()](~/api/Terminal.Gui.View.GetContentSize.yml). The [Layout Deep Dive](layout.md) has more details on the Content Area. + +* *[Viewport](~/api/Terminal.Gui.View.Viewport.yml)* A rectangle describing the portion of the *Content Area* that is currently visible to the user. It is a "portal" into the content. The `Viewport.Location` is relative to the top-left corner of the inner rectangle of `View.Padding`. If `Viewport.Size` is the same as `View.GetContentSize()`, `Viewport.Location` will be `0,0`. + ## Layout Terminal.Gui provides a rich system for how views are laid out relative to each other. The position of a view is set by setting the `X` and `Y` properties, which are of time [Pos](~/api/Terminal.Gui.Pos.yml). The size is set via `Width` and `Height`, which are of type [Dim](~/api/Terminal.Gui.Dim.yml). diff --git a/docfx/docs/scrolling.md b/docfx/docs/scrolling.md new file mode 100644 index 0000000000..06164d23c1 --- /dev/null +++ b/docfx/docs/scrolling.md @@ -0,0 +1,50 @@ +# Scrolling + +Terminal.Gui provides a rich system for how [View](View.md) users can scroll content with the keyboard and/or mouse. + +## Lexicon & Taxonomy + +See [View Deep Dive](View.md) for broader definitions. + +* *Scroll* (Verb) - The act of causing content to move either horizontally or vertically within the [View.Viewport](~/api/Terminal.Gui.View.Viewport.yml). Also referred to as "Content Scrolling". +* *[Scroll](~/api/Terminal.Gui.Scroll.yml)* (Noun) - 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 [View.Viewport](~/api/Terminal.Gui.View.Viewport.yml) and can be dragged with the mouse. A Scroll can be oriented either vertically or horizontally and is used within a [ScrollBar](~/api/Terminal.Gui.ScrollBar.yml). +* *ScrollSlider* - The visual indicator that shows the proportion of the scrollable content to the size of the [View.Viewport](~/api/Terminal.Gui.View.Viewport.yml) and allows the user to use the mouse to scroll. The Scroll Slider is not exposed publicly. +* *[ScrollBar](~/api/Terminal.Gui.ScrollBar.yml)* - Provides a visual indicator that content can be scrolled. ScrollBars consist of two buttons, one each for scrolling forward or backwards, a Scroll that can be clicked to scroll large amounts, and a ScrollSlider that can be dragged to scroll continuously. ScrollBars can be oriented either horizontally or vertically and support the user dragging and clicking with the mouse to scroll. + + +## Overview + +The ability to scroll content is built into View. The [View.Viewport](~/api/Terminal.Gui.View.Viewport.yml) represents the scrollable "viewport" into the View's Content Area (which is defined by the return value of [View.GetContentSize()](~/api/Terminal.Gui.View.GetContentSize.yml)). + +By default, [View](~/api/Terminal.Gui.View.yml), includes no bindings for the typical directional keyboard and mouse input and cause the Content Area. + +Terminal.Gui also provides the ability show a visual scroll bar that responds to mouse input. This ability is not enabled by default given how precious TUI screen real estate is. + +Scrolling with the mouse and keyboard are enabled by: + +1) Making the [View.Viewport](~/api/Terminal.Gui.View.Viewport.yml) size smaller than the size returned by [View.GetContentSize()](~/api/Terminal.Gui.View.GetContentSize.yml). +2) Creating key bindings for the appropriate directional keys (e.g. [Key.CursorDown](~/api/Terminal.Gui.Key)), and calling [View.ScrollHorizontal()](~/api/Terminal.Gui.View.ScrollHorizontal.yml)/[ScrollVertical()](~/api/Terminal.Gui.View.ScrollVertical.yml) as needed. +3) Subscribing to [View.MouseEvent](~/api/Terminal.Gui.View.MouseEvent.yml) and calling calling [View.ScrollHorizontal()](~/api/Terminal.Gui.View.ScrollHorizontal.yml)/[ScrollVertical()](~/api/Terminal.Gui.View.ScrollVertical.yml) as needed. +4) Enabling the [ScrollBar](~/api/Terminal.Gui.ScrollBar.yml)s built into View ([View.HorizontalScrollBar/VerticalScrollBar](~/api/Terminal.Gui.View.HorizontalScrollBar.yml)) by setting the flag [ViewportSettings.EnableScrollBars](~/api/Terminal.Gui.ViewportSettings.EnableScrollBars.yml) on [View.ViewportSettings](~/api/Terminal.Gui.View.ViewportSettings.yml). + +### [Viewport Settings](~/api/Terminal.Gui.ViewportSettings.yml) + +Use [View.ViewportSettings](~/api/Terminal.Gui.View.ViewportSettings.yml) to adjust the behavior of scrolling. + +* [AllowNegativeX/Y](~/api/Terminal.Gui.ViewportSettings.AllowNegativeXyml) - If set, Viewport.Size can be set to negative coordinates enabling scrolling beyond the top-left of the content area. + +* [AllowX/YGreaterThanContentWidth](~/api/Terminal.Gui.ViewportSettings.AllowXGreaterThanContentWidth) - If set, Viewport.Size can be set values greater than GetContentSize() enabling scrolling beyond the bottom-right of the Content Area. When not set, `Viewport.X/Y` are constrained to the dimension of the content area - 1. This means the last column of the content will remain visible even if there is an attempt to scroll the Viewport past the last column. The practical effect of this is that the last column/row of the content will always be visible. + +* [ClipContentOnly](~/api/Terminal.Gui.ViewportSettings.ClipContentOnly) - By default, clipping is applied to [Viewport](~/api/Terminal.Gui.View.Viewport.yml). Setting this flag will cause clipping to be applied to the visible content area. + +* [ClearContentOnly](~/api/Terminal.Gui.ViewportSettings.ClearContentOnly) - If set [View.Clear()](~/api/Terminal.Gui.View.Clear.yml) will clear only the portion of the content area that is visible within the Viewport. This is useful for views that have a content area larger than the Viewport and want the area outside the content to be visually distinct. + +* [EnableHorizontal/VerticalScrollBar](~/api/Terminal.Gui.ViewportSettings.EnableHorizontalScrollBar) - If set, the scroll bar will be enabled and automatically made visible when the corresponding dimension of [View.Viewport](~/api/Terminal.Gui.View.Viewport.yml) is smaller than the dimension of [View.GetContentSize()](~/api/Terminal.Gui.View.GetContentSize.yml). + + +### [ScrollBar](~/api/Terminal.Gui.ScrollBar.yml) + +Provides a visual indicator that content can be scrolled. ScrollBars consist of two buttons, one each for scrolling forward or backwards, a Scroll that can be clicked to scroll large amounts, and a ScrollSlider that can be dragged to scroll continuously. ScrollBars can be oriented either horizontally or vertically and support the user dragging and clicking with the mouse to scroll. + +While the *[Scroll](~/api/Terminal.Gui.Scroll.yml)* *[ScrollBar](~/api/Terminal.Gui.ScrollBar.yml)* Views can be used in a standalone manner to provide proportional scrolling, they are typically enabled automatically via the [View.HorizontalScrollBar](~/api/Terminal.Gui.View.HorizontalScrollBar.yml) and [View.VerticalScrollBar](~/api/Terminal.Gui.View.VerticalScrollBar.yml) properties. + From f8b2d0202718d81988cbb53777329b4a2f80b379 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 9 Sep 2024 11:57:24 -0600 Subject: [PATCH 079/128] Doc'd samples --- docfx/docs/scrolling.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docfx/docs/scrolling.md b/docfx/docs/scrolling.md index 06164d23c1..ff0d64583f 100644 --- a/docfx/docs/scrolling.md +++ b/docfx/docs/scrolling.md @@ -27,7 +27,18 @@ Scrolling with the mouse and keyboard are enabled by: 3) Subscribing to [View.MouseEvent](~/api/Terminal.Gui.View.MouseEvent.yml) and calling calling [View.ScrollHorizontal()](~/api/Terminal.Gui.View.ScrollHorizontal.yml)/[ScrollVertical()](~/api/Terminal.Gui.View.ScrollVertical.yml) as needed. 4) Enabling the [ScrollBar](~/api/Terminal.Gui.ScrollBar.yml)s built into View ([View.HorizontalScrollBar/VerticalScrollBar](~/api/Terminal.Gui.View.HorizontalScrollBar.yml)) by setting the flag [ViewportSettings.EnableScrollBars](~/api/Terminal.Gui.ViewportSettings.EnableScrollBars.yml) on [View.ViewportSettings](~/api/Terminal.Gui.View.ViewportSettings.yml). -### [Viewport Settings](~/api/Terminal.Gui.ViewportSettings.yml) +## Examples + +These Scenarios illustrate Terminal.Gui scrolling: + +* *Content Scrolling* - Demonstrates the various [Viewport Settings](~/api/Terminal.Gui.ViewportSettings.yml) (see below) in an interactive manner. Used by the development team to visually verify that convoluted View layout and arrangement scenarios scroll properly. +* *Character Map* - Demonstrates a sophisticated scrolling use-case. The entire set of Unicode code-points can be scrolled and searched. From a scrolling perspective, this Scenario illustrates how to manually configure `Viewport`, `SetContentArea()`, and `ViewportSettings` to enable horizontal and vertical headers (as might appear in a spreadsheet), full keyboard and mouse support, and more. +* *Scroll Demo* - Designed to demonstrate using the `Scroll` view in a standalone manner. +* *ScrollBar Demo* - Designed to demonstrate using the `ScrollBar` view in a standalone manner. +* *Scrolling* - A legacy Scenario from v1 that is used to visually test that scrolling is working properly. +* *ListView* and *TableView* - The source code to these built-in Views are good references for how to support scrolling and ScrollBars in a re-usable View sub-class. + +## [Viewport Settings](~/api/Terminal.Gui.ViewportSettings.yml) Use [View.ViewportSettings](~/api/Terminal.Gui.View.ViewportSettings.yml) to adjust the behavior of scrolling. @@ -42,7 +53,7 @@ Use [View.ViewportSettings](~/api/Terminal.Gui.View.ViewportSettings.yml) to adj * [EnableHorizontal/VerticalScrollBar](~/api/Terminal.Gui.ViewportSettings.EnableHorizontalScrollBar) - If set, the scroll bar will be enabled and automatically made visible when the corresponding dimension of [View.Viewport](~/api/Terminal.Gui.View.Viewport.yml) is smaller than the dimension of [View.GetContentSize()](~/api/Terminal.Gui.View.GetContentSize.yml). -### [ScrollBar](~/api/Terminal.Gui.ScrollBar.yml) +## [ScrollBar](~/api/Terminal.Gui.ScrollBar.yml) Provides a visual indicator that content can be scrolled. ScrollBars consist of two buttons, one each for scrolling forward or backwards, a Scroll that can be clicked to scroll large amounts, and a ScrollSlider that can be dragged to scroll continuously. ScrollBars can be oriented either horizontally or vertically and support the user dragging and clicking with the mouse to scroll. From 427f5b1e3d80369a1a576ab4b56633c6551a305c Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 9 Nov 2024 15:21:04 -0700 Subject: [PATCH 080/128] Refactoring... WIP 2 --- Terminal.Gui/Drawing/Thickness.cs | 2 +- Terminal.Gui/View/View.Drawing.Clipping.cs | 2 +- Terminal.Gui/View/View.ScrollBars.cs | 8 +- Terminal.Gui/Views/Scroll/Scroll.cs | 162 ++++++++++-- Terminal.Gui/Views/Scroll/ScrollBar.cs | 44 ++-- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 48 ++-- UICatalog/Scenarios/CharacterMap.cs | 213 +++++++--------- UICatalog/Scenarios/Editors/EventLog.cs | 34 +-- UICatalog/Scenarios/ScrollBarDemo.cs | 281 ++++++++++++++------- UICatalog/Scenarios/ScrollDemo.cs | 12 +- UnitTests/Views/ScrollBarTests.cs | 110 ++++---- UnitTests/Views/ScrollTests.cs | 68 ++--- 12 files changed, 582 insertions(+), 402 deletions(-) diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 0950f872bb..6aa2088e5a 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -236,7 +236,7 @@ public Rectangle GetInside (Rectangle rect) } /// - /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides + /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and right sides /// of the rectangle to half the specified value. /// public int Horizontal diff --git a/Terminal.Gui/View/View.Drawing.Clipping.cs b/Terminal.Gui/View/View.Drawing.Clipping.cs index 2996751f93..30f8b703c6 100644 --- a/Terminal.Gui/View/View.Drawing.Clipping.cs +++ b/Terminal.Gui/View/View.Drawing.Clipping.cs @@ -104,7 +104,7 @@ public static void SetClip (Region? region) if (this is Adornment adornment && adornment.Thickness != Thickness.Empty) { // Ensure adornments can't draw outside their thickness - frameRegion.Exclude (adornment.Thickness.GetInside (Frame)); + frameRegion.Exclude (adornment.Thickness.GetInside (FrameToScreen())); } SetClip (frameRegion); diff --git a/Terminal.Gui/View/View.ScrollBars.cs b/Terminal.Gui/View/View.ScrollBars.cs index a289885bad..cbea93ee45 100644 --- a/Terminal.Gui/View/View.ScrollBars.cs +++ b/Terminal.Gui/View/View.ScrollBars.cs @@ -43,7 +43,7 @@ private void SetupScrollBars () Bottom = scrollBar.Visible ? Padding.Thickness.Bottom + 1 : 0 }; - scrollBar.PositionChanged += (_, args) => + scrollBar.SliderPositionChanged += (_, args) => { Viewport = Viewport with { X = args.CurrentValue }; }; @@ -96,7 +96,7 @@ private void SetupScrollBars () Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : 0 }; - scrollBar.PositionChanged += (_, args) => + scrollBar.SliderPositionChanged += (_, args) => { Viewport = Viewport with { Y = args.CurrentValue }; }; @@ -120,12 +120,12 @@ private void SetupScrollBars () { if (_verticalScrollBar.IsValueCreated) { - _verticalScrollBar.Value.Position = Viewport.Y; + _verticalScrollBar.Value.SliderPosition = Viewport.Y; } if (_horizontalScrollBar.IsValueCreated) { - _horizontalScrollBar.Value.Position = Viewport.X; + _horizontalScrollBar.Value.SliderPosition = Viewport.X; } }; diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index e37b4c8dfd..42a3da02ce 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -133,7 +133,7 @@ protected virtual void OnSizeChanged (int size) { } /// Raised when has changed. public event EventHandler>? SizeChanged; - private int _position; + private int _sliderPosition; private void OnSliderOnFrameChanged (object? sender, EventArgs args) { @@ -142,52 +142,93 @@ private void OnSliderOnFrameChanged (object? sender, EventArgs args) return; } int framePos = Orientation == Orientation.Vertical ? args.CurrentValue.Y : args.CurrentValue.X; - double pos = ((double)ViewportDimension * ViewportDimension / (Size)) * framePos; - RaisePositionChangeEvents (_position, (int)pos); + RaisePositionChangeEvents (_sliderPosition, framePos); } /// - /// Gets or sets the position of the start of the Scroll slider, relative to . + /// Gets or sets the position of the start of the Scroll slider, within the Viewport. /// - public int Position + public int SliderPosition { - get => _position; - set => RaisePositionChangeEvents (_position, value); + get => _sliderPosition; + set => RaisePositionChangeEvents (_sliderPosition, value); } - private void RaisePositionChangeEvents (int current, int value) + /// + /// Gets or sets the position of the ScrollSlider within the range of 0.... + /// + public int ContentPosition + { + get + { + if (ViewportDimension - _slider.Size == 0) + { + return 0; + } + return (int)Math.Round (GetCurrentContentPosition ()); + } + set + { + if (Size - ViewportDimension == 0) + { + SliderPosition = 0; + return; + } + + double newContentPos = (double)value / (ViewportDimension - _slider.Size) * (Size - ViewportDimension); + double newSliderPos = (double)value / (Size - ViewportDimension) * (ViewportDimension - _slider.Size); + + int newSliderPosition = (int)Math.Ceiling (newSliderPos); + if (newContentPos >= GetCurrentContentPosition ()) + { + newSliderPosition = (int)Math.Floor (newSliderPos); + } + + if (SliderPosition != newSliderPosition) + { + SliderPosition = newSliderPosition; + } + } + } + + private double GetCurrentContentPosition () { - if (OnPositionChanging (current, value)) + return (double)SliderPosition / (ViewportDimension - _slider.Size) * (Size - ViewportDimension); + } + + private void RaisePositionChangeEvents (int currentSliderPosition, int newSliderPosition) + { + if (OnPositionChanging (currentSliderPosition, newSliderPosition)) { - _slider.Position = current; + _slider.Position = currentSliderPosition; return; } - CancelEventArgs args = new (ref current, ref value); - PositionChanging?.Invoke (this, args); + CancelEventArgs args = new (ref currentSliderPosition, ref newSliderPosition); + SliderPositionChanging?.Invoke (this, args); if (args.Cancel) { - _slider.Position = current; + _slider.Position = currentSliderPosition; return; } // This sets the slider position and clamps the value - _slider.Position = value; + _slider.Position = newSliderPosition; - if (_slider.Position == _position) + if (_slider.Position == _sliderPosition) { return; } - _position = value; + _sliderPosition = newSliderPosition; - OnPositionChanged (_position); - PositionChanged?.Invoke (this, new (in value)); + OnPositionChanged (_sliderPosition); + SliderPositionChanged?.Invoke (this, new (in newSliderPosition)); } /// - /// Called when is changing. Return true to cancel the change. + /// Called when is changing. Return true to cancel the change. /// protected virtual bool OnPositionChanging (int currentPos, int newPos) { @@ -195,16 +236,16 @@ protected virtual bool OnPositionChanging (int currentPos, int newPos) } /// - /// Raised when the is changing. Set to + /// Raised when the is changing. Set to /// to prevent the position from being changed. /// - public event EventHandler>? PositionChanging; + public event EventHandler>? SliderPositionChanging; - /// Called when has changed. + /// Called when has changed. protected virtual void OnPositionChanged (int position) { } - /// Raised when the has changed. - public event EventHandler>? PositionChanged; + /// Raised when the has changed. + public event EventHandler>? SliderPositionChanged; /// @@ -215,6 +256,77 @@ protected override bool OnClearingViewport () return true; } + /// + protected override bool OnMouseClick (MouseEventArgs args) + { + if (!args.IsSingleClicked) + { + return false; + } + + if (Orientation == Orientation.Vertical) + { + // If the position is w/in the slider frame ignore + if (args.Position.Y >= _slider.Frame.Y && args.Position.Y < _slider.Frame.Y + _slider.Frame.Height) + { + return false; + } + + SliderPosition = args.Position.Y; + } + else + { + // If the position is w/in the slider frame ignore + if (args.Position.X >= _slider.Frame.X && args.Position.X < _slider.Frame.X + _slider.Frame.Width) + { + return false; + } + + SliderPosition = args.Position.X; + } + + + return true; + } + + /// + protected override bool OnMouseEvent (MouseEventArgs mouseEvent) + { + if (SuperView is null) + { + return false; + } + + if (!mouseEvent.IsWheel) + { + return false; + } + + if (Orientation == Orientation.Vertical) + { + if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledDown)) + { + SliderPosition++; + } + if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledUp)) + { + SliderPosition--; + } + } + else + { + if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledRight)) + { + SliderPosition++; + } + if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledLeft)) + { + SliderPosition--; + } + } + + return true; + } /// public bool EnableForDesign () @@ -236,7 +348,7 @@ public bool EnableForDesign () Width = 1; Height = Dim.Fill (); Size = 1000; - Position = 10; + SliderPosition = 10; diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index e97d1f727e..334cd5cf2f 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -12,7 +12,7 @@ namespace Terminal.Gui; /// /// /// -/// indicates the number of rows or columns the Scroll has moved from 0. +/// indicates the number of rows or columns the Scroll has moved from 0. /// /// public class ScrollBar : View, IOrientation, IDesignable @@ -27,8 +27,8 @@ public ScrollBar () CanFocus = false; _scroll = new (); - _scroll.PositionChanging += OnScrollOnPositionChanging; - _scroll.PositionChanged += OnScrollOnPositionChanged; + _scroll.SliderPositionChanging += OnScrollOnSliderPositionChanging; + _scroll.SliderPositionChanged += OnScrollOnSliderPositionChanged; _scroll.SizeChanged += OnScrollOnSizeChanged; _decreaseButton = new () @@ -64,13 +64,13 @@ public ScrollBar () void OnDecreaseButtonOnAccept (object? s, CommandEventArgs e) { - _scroll.Position--; + _scroll.ContentPosition--; e.Cancel = true; } void OnIncreaseButtonOnAccept (object? s, CommandEventArgs e) { - _scroll.Position++; + _scroll.ContentPosition++; e.Cancel = true; } } @@ -180,25 +180,26 @@ public bool KeepContentInAllViewport set; } - /// Gets or sets the position, relative to , to set the scrollbar at. + /// Gets or sets the position of the slider within the ScrollBar's Viewport. /// The position. - public int Position + public int SliderPosition { - get => _scroll.Position; - set => _scroll.Position = value; + get => _scroll.SliderPosition; + set => _scroll.SliderPosition = value; } - private void OnScrollOnPositionChanged (object? sender, EventArgs e) { PositionChanged?.Invoke (this, e); } - private void OnScrollOnPositionChanging (object? sender, CancelEventArgs e) { PositionChanging?.Invoke (this, e); } - - /// Raised when the has changed. - public event EventHandler>? PositionChanged; + private void OnScrollOnSliderPositionChanged (object? sender, EventArgs e) { SliderPositionChanged?.Invoke (this, e); } + private void OnScrollOnSliderPositionChanging (object? sender, CancelEventArgs e) { SliderPositionChanging?.Invoke (this, e); } /// - /// Raised when the is changing. Set to + /// Raised when the is changing. Set to /// to prevent the position from being changed. /// - public event EventHandler>? PositionChanging; + public event EventHandler>? SliderPositionChanging; + + /// Raised when the has changed. + public event EventHandler>? SliderPositionChanged; + /// /// Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through. @@ -209,6 +210,15 @@ public int Size set => _scroll.Size = value; } + /// + /// Gets or sets the position of the ScrollSlider within the range of 0.... + /// + public int ContentPosition + { + get => _scroll.ContentPosition; + set => _scroll.ContentPosition = value; + } + /// Raised when has changed. public event EventHandler>? SizeChanged; @@ -279,7 +289,7 @@ public bool EnableForDesign () Width = 1; Height = Dim.Fill (); Size = 200; - Position = 10; + SliderPosition = 10; //ShowPercent = true; return true; } diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 759734413a..5d8f01ffe8 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -91,11 +91,21 @@ protected override bool OnClearingViewport () return true; } + private bool _showPercent; + /// /// Gets or sets whether the ScrollSlider will set to show the percentage the slider /// takes up within the 's Viewport. /// - public bool ShowPercent { get; set; } + public bool ShowPercent + { + get => _showPercent; + set + { + _showPercent = value; + SetNeedsDraw(); + } + } /// /// Gets or sets the size of the ScrollSlider. This is a helper that simply gets or sets the Width or Height depending on the @@ -138,7 +148,7 @@ public int Size } /// - /// Gets or sets the position of the ScrollSlider. This is a helper that simply gets or sets the X or Y depending on the + /// Gets or sets the position of the ScrollSlider relative to the size of the ScrollSlider's Frame. This is a helper that simply gets or sets the X or Y depending on the /// . The position will be constrained such that the ScrollSlider will not go outside the Viewport of /// the . /// @@ -175,6 +185,8 @@ protected override bool OnDrawingText () { if (!ShowPercent) { + Text = string.Empty; + return false; } @@ -246,38 +258,10 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) Application.UngrabMouse (); } } + return true; } + return false; - if (mouseEvent.IsWheel) - { - if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledDown) || mouseEvent.Flags.HasFlag (MouseFlags.WheeledRight)) - { - offset = 1; - } - else if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledDown) || mouseEvent.Flags.HasFlag (MouseFlags.WheeledLeft)) - { - offset = -1; - } - - if (Orientation == Orientation.Vertical) - { - Y = Frame.Y + offset < 0 - ? 0 - : Frame.Y + offset + Frame.Height > superViewDimension - ? Math.Max (superViewDimension - Frame.Height, 0) - : Frame.Y + offset; - } - else - { - X = Frame.X + offset < 0 - ? 0 - : Frame.X + offset + Frame.Width > superViewDimension - ? Math.Max (superViewDimension - Frame.Width, 0) - : Frame.X + offset; - } - } - - return true; } /// diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 0890aa9114..f069ca457d 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -10,7 +10,9 @@ using System.Text.Json; using System.Text.Unicode; using System.Threading.Tasks; +using JetBrains.Annotations; using Terminal.Gui; +using static System.Runtime.InteropServices.JavaScript.JSType; using static Terminal.Gui.SpinnerStyle; namespace UICatalog.Scenarios; @@ -335,84 +337,50 @@ internal class CharMap : View, IDesignable private int _selected; private int _start; + private readonly ScrollBar _vScrollBar; + private readonly ScrollBar _hScrollBar; + public CharMap () { ColorScheme = Colors.ColorSchemes ["Dialog"]; CanFocus = true; CursorVisibility = CursorVisibility.Default; + //ViewportSettings = ViewportSettings.AllowLocationGreaterThanContentSize; - SetContentSize (new (RowWidth, (_maxCodePoint / 16 + 1) * _rowHeight)); + SetContentSize (new (COLUMN_WIDTH * 16, (_maxCodePoint / 16) * _rowHeight)); // +1 for Header AddCommand ( - Command.ScrollUp, + Command.Up, () => { - if (SelectedCodePoint >= 16) - { - SelectedCodePoint -= 16; - } - - ScrollVertical (-_rowHeight); - + SelectedCodePoint -= 16; return true; } ); AddCommand ( - Command.ScrollDown, + Command.Down, () => { - if (SelectedCodePoint <= _maxCodePoint - 16) - { - SelectedCodePoint += 16; - } - - if (Cursor.Y >= Viewport.Height) - { - ScrollVertical (_rowHeight); - } - + SelectedCodePoint += 16; return true; } ); AddCommand ( - Command.ScrollLeft, + Command.Left, () => { - if (SelectedCodePoint > 0) - { - SelectedCodePoint--; - } - - if (Cursor.X > RowLabelWidth + 1) - { - ScrollHorizontal (-COLUMN_WIDTH); - } - - if (Cursor.X >= Viewport.Width) - { - ScrollHorizontal (Cursor.X - Viewport.Width + 1); - } - + SelectedCodePoint--; return true; } ); AddCommand ( - Command.ScrollRight, + Command.Right, () => { - if (SelectedCodePoint < _maxCodePoint) - { - SelectedCodePoint++; - } - - if (Cursor.X >= Viewport.Width) - { - ScrollHorizontal (COLUMN_WIDTH); - } - + SelectedCodePoint++; return true; } ); @@ -422,8 +390,7 @@ public CharMap () () => { int page = (Viewport.Height - 1 / _rowHeight) * 16; - SelectedCodePoint -= Math.Min (page, SelectedCodePoint); - Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight }; + SelectedCodePoint -= page; return true; } @@ -434,8 +401,7 @@ public CharMap () () => { int page = (Viewport.Height - 1 / _rowHeight) * 16; - SelectedCodePoint += Math.Min (page, _maxCodePoint - SelectedCodePoint); - Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight }; + SelectedCodePoint += page; return true; } @@ -456,7 +422,6 @@ public CharMap () () => { SelectedCodePoint = _maxCodePoint; - Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight }; return true; } @@ -472,10 +437,10 @@ public CharMap () } ); - KeyBindings.Add (Key.CursorUp, Command.ScrollUp); - KeyBindings.Add (Key.CursorDown, Command.ScrollDown); - KeyBindings.Add (Key.CursorLeft, Command.ScrollLeft); - KeyBindings.Add (Key.CursorRight, Command.ScrollRight); + KeyBindings.Add (Key.CursorUp, Command.Up); + KeyBindings.Add (Key.CursorDown, Command.Down); + KeyBindings.Add (Key.CursorLeft, Command.Left); + KeyBindings.Add (Key.CursorRight, Command.Right); KeyBindings.Add (Key.PageUp, Command.PageUp); KeyBindings.Add (Key.PageDown, Command.PageDown); KeyBindings.Add (Key.Home, Command.Start); @@ -487,17 +452,17 @@ public CharMap () // Add scrollbars Padding.Thickness = new (0, 0, 1, 0); - ScrollBar hScrollBar = new () + _hScrollBar = new () { AutoHide = false, X = RowLabelWidth + 1, Y = Pos.AnchorEnd (), Orientation = Orientation.Horizontal, Width = Dim.Fill (1), - Size = COLUMN_WIDTH * 15 + Size = GetContentSize ().Width }; - ScrollBar vScrollBar = new () + _vScrollBar = new () { AutoHide = false, X = Pos.AnchorEnd (), @@ -506,42 +471,37 @@ public CharMap () Size = GetContentSize ().Height }; - Padding.Add (vScrollBar, hScrollBar); + Padding.Add (_vScrollBar, _hScrollBar); - vScrollBar.PositionChanged += (sender, args) => + _vScrollBar.SliderPositionChanged += (sender, args) => { if (Viewport.Height > 0) { - Viewport = Viewport with { Y = args.CurrentValue }; + Viewport = Viewport with { Y = _vScrollBar.ContentPosition }; } }; - hScrollBar.PositionChanged += (sender, args) => - { - if (Viewport.Width > 0) - { - Viewport = Viewport with { X = args.CurrentValue }; - } - }; + _hScrollBar.SliderPositionChanged += (sender, args) => + { + if (Viewport.Width > 0) + { + Viewport = Viewport with { X = _hScrollBar.ContentPosition }; + } + }; - ViewportChanged += (sender, args) => + FrameChanged += (sender, args) => { - if (Viewport.Width < GetContentSize ().Width) + int width = Viewport.Width / COLUMN_WIDTH * COLUMN_WIDTH - RowLabelWidth; + if (width < GetContentSize ().Width) { Padding.Thickness = Padding.Thickness with { Bottom = 1 }; - hScrollBar.Visible = true; } else { Padding.Thickness = Padding.Thickness with { Bottom = 0 }; - hScrollBar.Visible = false; } - - hScrollBar.Size = COLUMN_WIDTH * 15; - hScrollBar.Position = Viewport.X; - - vScrollBar.Size = GetContentSize ().Height; - vScrollBar.Position = Viewport.Y; + _hScrollBar.ContentPosition = Viewport.X; + _vScrollBar.ContentPosition = Viewport.Y; }; } @@ -550,6 +510,7 @@ private void Handle_MouseEvent (object sender, MouseEventArgs e) if (e.Flags == MouseFlags.WheeledDown) { ScrollVertical (1); + _vScrollBar.ContentPosition = Viewport.Y; e.Handled = true; return; @@ -558,6 +519,7 @@ private void Handle_MouseEvent (object sender, MouseEventArgs e) if (e.Flags == MouseFlags.WheeledUp) { ScrollVertical (-1); + _vScrollBar.ContentPosition = Viewport.Y; e.Handled = true; return; @@ -565,7 +527,7 @@ private void Handle_MouseEvent (object sender, MouseEventArgs e) if (e.Flags == MouseFlags.WheeledRight) { - ScrollHorizontal (1); + _hScrollBar.SliderPosition++; e.Handled = true; return; @@ -573,18 +535,17 @@ private void Handle_MouseEvent (object sender, MouseEventArgs e) if (e.Flags == MouseFlags.WheeledLeft) { - ScrollHorizontal (-1); + _hScrollBar.SliderPosition--; e.Handled = true; } } - /// Gets the coordinates of the Cursor based on the SelectedCodePoint in screen coordinates + /// Gets or sets the coordinates of the Cursor based on the SelectedCodePoint in Viewport-relative coordinates public Point Cursor { get { - int row = SelectedCodePoint / 16 * _rowHeight - Viewport.Y + 1; - + int row = SelectedCodePoint / 16 * _rowHeight - Viewport.Y + 1; // + 1 for header int col = SelectedCodePoint % 16 * COLUMN_WIDTH - Viewport.X + RowLabelWidth + 1; // + 1 for padding between label and first column return new (col, row); @@ -595,8 +556,7 @@ public Point Cursor public static int _maxCodePoint = UnicodeRange.Ranges.Max (r => r.End); /// - /// Specifies the starting offset for the character map. The default is 0x2500 which is the Box Drawing - /// characters. + /// Gets or sets the currently selected codepoint. Causes the Viewport to scroll to make the selected code point visible. /// public int SelectedCodePoint { @@ -608,43 +568,61 @@ public int SelectedCodePoint return; } - _selected = value; + Point prevCursor = Cursor; + int newSelectedCodePoint = Math.Clamp (value, 0, _maxCodePoint); + + ScrollToMakeRowVisible (newSelectedCodePoint / 16 * _rowHeight, prevCursor.Y); + ScrollToMakeColumnVisible (newSelectedCodePoint % 16 * COLUMN_WIDTH, prevCursor.X); - if (IsInitialized) + if (_selected != newSelectedCodePoint) { - int row = SelectedCodePoint / 16 * _rowHeight; - int col = SelectedCodePoint % 16 * COLUMN_WIDTH; + _selected = newSelectedCodePoint; + SetNeedsDraw (); + SelectedCodePointChanged?.Invoke (this, new (SelectedCodePoint, null)); + } + } + } - if (row - Viewport.Y < 0) - { - // Moving up. - Viewport = Viewport with { Y = row }; - } - else if (row - Viewport.Y >= Viewport.Height) - { - // Moving down. - Viewport = Viewport with { Y = row - Viewport.Height }; - } + private void ScrollToMakeRowVisible (int row, int prevCursorY) + { + int height = Viewport.Height - 1; // Header + int delta = row - (Viewport.Y + height); + int scroll = Viewport.Height - (prevCursorY - delta); - int width = Viewport.Width / COLUMN_WIDTH * COLUMN_WIDTH - RowLabelWidth; + if (row - Viewport.Y < 0) + { + // Moving up. + Viewport = Viewport with { Y = Viewport.Y + (row - Viewport.Y) }; + } + else if (row - Viewport.Y >= height) + { + // Moving down. + Viewport = Viewport with { Y = Math.Min (Viewport.Y + scroll, GetContentSize ().Height - height ) }; + } + _vScrollBar.ContentPosition = row; + } - if (col - Viewport.X < 0) - { - // Moving left. - Viewport = Viewport with { X = col }; - } - else if (col - Viewport.X >= width) - { - // Moving right. - Viewport = Viewport with { X = col - width }; - } - } + private void ScrollToMakeColumnVisible (int col, int prevCursorX) + { + int width = Viewport.Width - RowLabelWidth; + int delta = col - (Viewport.X + width); + int scroll = Viewport.Width - (prevCursorX - delta); - SetNeedsDraw (); - SelectedCodePointChanged?.Invoke (this, new (SelectedCodePoint, null)); + if (col - Viewport.X < 0) + { + // Moving left. + Viewport = Viewport with { X = Viewport.X + (col - Viewport.X) }; + _hScrollBar.ContentPosition = col; + } + else if (col - Viewport.X >= width) + { + // Moving right. + Viewport = Viewport with { X = Math.Min (Viewport.X + scroll, GetContentSize ().Width - width) }; + _hScrollBar.ContentPosition = col; } } + public bool ShowGlyphWidths { get => _rowHeight == 2; @@ -682,8 +660,6 @@ protected override bool OnDrawingContent () return true; } - ClearViewport (); - int cursorCol = Cursor.X + Viewport.X - RowLabelWidth - 1; int cursorRow = Cursor.Y + Viewport.Y - 1; @@ -710,8 +686,7 @@ protected override bool OnDrawingContent () } } - // Even though the Clip is set to prevent us from drawing on the row potentially occupied by the horizontal - // scroll bar, we do the smart thing and not actually draw that row if not necessary. + // Start at 1 because Header. for (var y = 1; y < Viewport.Height; y++) { // What row is this? @@ -1022,7 +997,7 @@ private void ShowDetails () document.RootElement, new JsonSerializerOptions - { WriteIndented = true } + { WriteIndented = true } ); } diff --git a/UICatalog/Scenarios/Editors/EventLog.cs b/UICatalog/Scenarios/Editors/EventLog.cs index e586b127da..85c834c00f 100644 --- a/UICatalog/Scenarios/Editors/EventLog.cs +++ b/UICatalog/Scenarios/Editors/EventLog.cs @@ -22,7 +22,14 @@ public EventLog () X = Pos.AnchorEnd (); Y = 0; - Width = Dim.Func (() => Math.Min (SuperView!.Viewport.Width / 3, MaxLength + GetAdornmentsThickness ().Horizontal)); + Width = Dim.Func (() => + { + if (!IsInitialized) + { + return 0; + } + return Math.Min (SuperView!.Viewport.Width / 3, MaxLength + GetAdornmentsThickness ().Horizontal); + }); Height = Dim.Fill (); ExpandButton = new () @@ -55,39 +62,36 @@ public View? ViewToLog _viewToLog.Initialized += (s, args) => { View? sender = s as View; - _eventSource.Add ($"Initialized: {GetIdentifyingString (sender)}"); - MoveEnd (); + Log ($"Initialized: {GetIdentifyingString (sender)}"); }; _viewToLog.MouseClick += (s, args) => { - View? sender = s as View; - _eventSource.Add ($"MouseClick: {args}"); - MoveEnd (); + Log ($"MouseClick: {args}"); }; _viewToLog.HandlingHotKey += (s, args) => { - View? sender = s as View; - _eventSource.Add ($"HandlingHotKey: {args.Context.Command} {args.Context.Data}"); - MoveEnd (); + Log ($"HandlingHotKey: {args.Context.Command} {args.Context.Data}"); }; _viewToLog.Selecting += (s, args) => { - View? sender = s as View; - _eventSource.Add ($"Selecting: {args.Context.Command} {args.Context.Data}"); - MoveEnd (); + Log ($"Selecting: {args.Context.Command} {args.Context.Data}"); }; _viewToLog.Accepting += (s, args) => { - View? sender = s as View; - _eventSource.Add ($"Accepting: {args.Context.Command} {args.Context.Data}"); - MoveEnd (); + Log ($"Accepting: {args.Context.Command} {args.Context.Data}"); }; } } } + public void Log (string text) + { + _eventSource.Add (text); + MoveEnd (); + } + private void EventLog_Initialized (object? _, EventArgs e) { diff --git a/UICatalog/Scenarios/ScrollBarDemo.cs b/UICatalog/Scenarios/ScrollBarDemo.cs index b36e1ece71..dc13b91c14 100644 --- a/UICatalog/Scenarios/ScrollBarDemo.cs +++ b/UICatalog/Scenarios/ScrollBarDemo.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Terminal.Gui; namespace UICatalog.Scenarios; @@ -22,7 +23,7 @@ public override void Main () var editor = new AdornmentsEditor (); app.Add (editor); - var view = new FrameView + var frameView = new FrameView { Title = "Demo View", X = Pos.Right (editor), @@ -30,25 +31,43 @@ public override void Main () Height = Dim.Fill (), ColorScheme = Colors.ColorSchemes ["Base"] }; - app.Add (view); + app.Add (frameView); var scrollBar = new ScrollBar { X = Pos.AnchorEnd (), + AutoHide = false //ShowPercent = true }; - view.Add (scrollBar); + frameView.Add (scrollBar); app.Loaded += (s, e) => { scrollBar.Size = scrollBar.Viewport.Height; }; + int GetMaxLabelWidth (int groupId) + { + return frameView.Subviews.Max ( + v => + { + if (v.Y.Has (out var pos) && pos.GroupId == groupId) + { + return v.Text.GetColumns (); + } + + return 0; + }); + } + var lblWidthHeight = new Label { - Text = "Width/Height:" + Text = "_Width/Height:", + TextAlignment = Alignment.End, + Y = Pos.Align (Alignment.Start, AlignmentModes.StartToEnd, groupId: 1), + Width = Dim.Func (() => GetMaxLabelWidth (1)) }; - view.Add (lblWidthHeight); + frameView.Add (lblWidthHeight); NumericUpDown scrollWidthHeight = new () { @@ -56,7 +75,7 @@ public override void Main () X = Pos.Right (lblWidthHeight) + 1, Y = Pos.Top (lblWidthHeight) }; - view.Add (scrollWidthHeight); + frameView.Add (scrollWidthHeight); scrollWidthHeight.ValueChanging += (s, e) => { @@ -82,13 +101,24 @@ public override void Main () } }; + + var lblOrientationabel = new Label + { + Text = "_Orientation:", + TextAlignment = Alignment.End, + Y = Pos.Align (Alignment.Start, groupId: 1), + Width = Dim.Func (() => GetMaxLabelWidth (1)) + }; + frameView.Add (lblOrientationabel); + var rgOrientation = new RadioGroup { - Y = Pos.Bottom (lblWidthHeight), + X = Pos.Right (lblOrientationabel) + 1, + Y = Pos.Top (lblOrientationabel), RadioLabels = ["Vertical", "Horizontal"], Orientation = Orientation.Horizontal }; - view.Add (rgOrientation); + frameView.Add (rgOrientation); rgOrientation.SelectedItemChanged += (s, e) => { @@ -104,7 +134,6 @@ public override void Main () scrollBar.Y = 0; scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scrollBar.SuperView.GetContentSize ().Width); scrollBar.Width = scrollWidthHeight.Value; - scrollBar.Height = Dim.Fill (); scrollBar.Size /= 3; } else @@ -112,8 +141,6 @@ public override void Main () scrollBar.Orientation = Orientation.Horizontal; scrollBar.X = 0; scrollBar.Y = Pos.AnchorEnd (); - scrollBar.Width = Dim.Fill (); - scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scrollBar.SuperView.GetContentSize ().Height); scrollBar.Height = scrollWidthHeight.Value; scrollBar.Size *= 3; @@ -122,10 +149,12 @@ public override void Main () var lblSize = new Label { - Y = Pos.Bottom (rgOrientation), - Text = "Size:" + Text = "_Size:", + TextAlignment = Alignment.End, + Y = Pos.Align (Alignment.Start, groupId: 1), + Width = Dim.Func (() => GetMaxLabelWidth (1)) }; - view.Add (lblSize); + frameView.Add (lblSize); NumericUpDown scrollSize = new () { @@ -133,7 +162,7 @@ public override void Main () X = Pos.Right (lblSize) + 1, Y = Pos.Top (lblSize) }; - view.Add (scrollSize); + frameView.Add (scrollSize); scrollSize.ValueChanging += (s, e) => { @@ -150,123 +179,189 @@ public override void Main () } }; - var lblPosition = new Label + var lblSliderPosition = new Label { - Y = Pos.Bottom (lblSize), - Text = "Position:" + Text = "_SliderPosition:", + TextAlignment = Alignment.End, + Y = Pos.Align (Alignment.Start, groupId: 1), + Width = Dim.Func (() => GetMaxLabelWidth (1)) + }; - view.Add (lblPosition); + frameView.Add (lblSliderPosition); - NumericUpDown scrollPosition = new () + NumericUpDown scrollSliderPosition = new () { - Value = scrollBar.Position, - X = Pos.Right (lblPosition) + 1, - Y = Pos.Top (lblPosition) + Value = scrollBar.SliderPosition, + X = Pos.Right (lblSliderPosition) + 1, + Y = Pos.Top (lblSliderPosition) }; - view.Add (scrollPosition); + frameView.Add (scrollSliderPosition); - scrollPosition.ValueChanging += (s, e) => - { - if (e.NewValue < 0) - { - e.Cancel = true; + scrollSliderPosition.ValueChanging += (s, e) => + { + if (e.NewValue < 0) + { + e.Cancel = true; - return; - } + return; + } - if (scrollBar.Position != e.NewValue) - { - scrollBar.Position = e.NewValue; - } + if (scrollBar.SliderPosition != e.NewValue) + { + scrollBar.SliderPosition = e.NewValue; + } - if (scrollBar.Position != e.NewValue) - { - e.Cancel = true; - } - }; + if (scrollBar.SliderPosition != e.NewValue) + { + e.Cancel = true; + } + }; - var ckbAutoHide = new CheckBox - { Y = Pos.Bottom (scrollPosition), Text = "AutoHideScrollBar", CheckedState = scrollBar.AutoHide ? CheckState.Checked : CheckState.UnChecked }; - ckbAutoHide.CheckedStateChanging += (s, e) => scrollBar.AutoHide = e.NewValue == CheckState.Checked; - view.Add (ckbAutoHide); + var lblContentPosition = new Label + { + Text = "_ContentPosition:", + TextAlignment = Alignment.End, + Y = Pos.Align (Alignment.Start, groupId: 1), + Width = Dim.Func (() => GetMaxLabelWidth (1)) - //var ckbKeepContentInAllViewport = new CheckBox - //{ - // X = Pos.Right (ckbShowScrollIndicator) + 1, Y = Pos.Bottom (scrollPosition), Text = "KeepContentInAllViewport", - // CheckedState = scrollBar.KeepContentInAllViewport ? CheckState.Checked : CheckState.UnChecked - //}; - //ckbKeepContentInAllViewport.CheckedStateChanging += (s, e) => scrollBar.KeepContentInAllViewport = e.NewValue == CheckState.Checked; - //view.Add (ckbKeepContentInAllViewport); + }; + frameView.Add (lblContentPosition); - var lblSizeChanged = new Label + NumericUpDown scrollContentPosition = new () { - Y = Pos.Bottom (ckbAutoHide) + 1 + Value = scrollBar.SliderPosition, + X = Pos.Right (lblContentPosition) + 1, + Y = Pos.Top (lblContentPosition) }; - view.Add (lblSizeChanged); + frameView.Add (scrollContentPosition); - scrollBar.SizeChanged += (s, e) => - { - lblSizeChanged.Text = $"SizeChanged event - CurrentValue: {e.CurrentValue}"; + scrollContentPosition.ValueChanging += (s, e) => + { + if (e.NewValue < 0) + { + e.Cancel = true; - if (scrollSize.Value != e.CurrentValue) - { - scrollSize.Value = e.CurrentValue; - } - }; + return; + } + + if (scrollBar.ContentPosition != e.NewValue) + { + scrollBar.ContentPosition = e.NewValue; + } - var lblPosChanging = new Label + if (scrollBar.ContentPosition != e.NewValue) + { + e.Cancel = true; + } + }; + + var lblOptions = new Label { - Y = Pos.Bottom (lblSizeChanged) + Text = "_Options:", + TextAlignment = Alignment.End, + Y = Pos.Align (Alignment.Start, groupId: 1), + Width = Dim.Func (() => GetMaxLabelWidth (1)) }; - view.Add (lblPosChanging); - - scrollBar.PositionChanging += (s, e) => { lblPosChanging.Text = $"PositionChanging event - CurrentValue: {e.CurrentValue}; NewValue: {e.NewValue}"; }; + frameView.Add (lblOptions); + var ckbAutoHide = new CheckBox + { + Y = Pos.Top (lblOptions), + X = Pos.Right (lblOptions) + 1, + Text = "Auto_HideScrollBar", + CheckedState = scrollBar.AutoHide ? CheckState.Checked : CheckState.UnChecked + }; + ckbAutoHide.CheckedStateChanging += (s, e) => scrollBar.AutoHide = e.NewValue == CheckState.Checked; + frameView.Add (ckbAutoHide); - var lblPositionChanged = new Label + var ckbShowPercent = new CheckBox { - Y = Pos.Bottom (lblPosChanging) + Y = Pos.Top (lblOptions), + X = Pos.Right (ckbAutoHide) + 1, + Text = "Sho_wPercent", + CheckedState = scrollBar.ShowPercent ? CheckState.Checked : CheckState.UnChecked }; - view.Add (lblPositionChanged); + ckbShowPercent.CheckedStateChanging += (s, e) => scrollBar.ShowPercent = e.NewValue == CheckState.Checked; + frameView.Add (ckbShowPercent); - scrollBar.PositionChanged += (s, e) => - { - lblPositionChanged.Text = $"PositionChanged event - CurrentValue: {e.CurrentValue}"; - scrollPosition.Value = e.CurrentValue; - }; + //var ckbKeepContentInAllViewport = new CheckBox + //{ + // X = Pos.Right (ckbShowScrollIndicator) + 1, Y = Pos.Bottom (scrollPosition), Text = "KeepContentInAllViewport", + // CheckedState = scrollBar.KeepContentInAllViewport ? CheckState.Checked : CheckState.UnChecked + //}; + //ckbKeepContentInAllViewport.CheckedStateChanging += (s, e) => scrollBar.KeepContentInAllViewport = e.NewValue == CheckState.Checked; + //view.Add (ckbKeepContentInAllViewport); var lblScrollFrame = new Label { - Y = Pos.Bottom (lblPositionChanged) + 1 + Y = Pos.Bottom (lblOptions) + 1 }; - view.Add (lblScrollFrame); + frameView.Add (lblScrollFrame); var lblScrollViewport = new Label { Y = Pos.Bottom (lblScrollFrame) }; - view.Add (lblScrollViewport); + frameView.Add (lblScrollViewport); var lblScrollContentSize = new Label { Y = Pos.Bottom (lblScrollViewport) }; - view.Add (lblScrollContentSize); - + frameView.Add (lblScrollContentSize); scrollBar.SubviewsLaidOut += (s, e) => - { - lblScrollFrame.Text = $"ScrollBar Frame: {scrollBar.Frame.ToString ()}"; - lblScrollViewport.Text = $"ScrollBar Viewport: {scrollBar.Viewport.ToString ()}"; - lblScrollContentSize.Text = $"ScrollBar ContentSize: {scrollBar.GetContentSize ().ToString ()}"; - }; - - editor.Initialized += (s, e) => - { - scrollBar.Size = int.Max (app.GetContentSize ().Height * 2, app.GetContentSize ().Width * 2); - editor.ViewToEdit = scrollBar; - }; - - app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags; + { + lblScrollFrame.Text = $"Scroll Frame: {scrollBar.Frame.ToString ()}"; + lblScrollViewport.Text = $"Scroll Viewport: {scrollBar.Viewport.ToString ()}"; + lblScrollContentSize.Text = $"Scroll ContentSize: {scrollBar.GetContentSize ().ToString ()}"; + }; + + EventLog eventLog = new () + { + X = Pos.AnchorEnd () - 1, + Y = 0, + Height = Dim.Height (frameView), + BorderStyle = LineStyle.Single, + ViewToLog = scrollBar + }; + app.Add (eventLog); + frameView.Width = Dim.Fill (Dim.Func (() => Math.Max (28, eventLog.Frame.Width + 1))); + + app.Initialized += AppOnInitialized; + + + void AppOnInitialized (object sender, EventArgs e) + { + scrollBar.SizeChanged += (s, e) => + { + eventLog.Log ($"SizeChanged: {e.CurrentValue}"); + + if (scrollSize.Value != e.CurrentValue) + { + scrollSize.Value = e.CurrentValue; + } + }; + + scrollBar.SliderPositionChanging += (s, e) => + { + eventLog.Log ($"SliderPositionChanging: {e.CurrentValue}"); + eventLog.Log ($" NewValue: {e.NewValue}"); + }; + + scrollBar.SliderPositionChanged += (s, e) => + { + eventLog.Log ($"SliderPositionChanged: {e.CurrentValue}"); + eventLog.Log ($" ContentPosition: {scrollBar.ContentPosition}"); + scrollSliderPosition.Value = e.CurrentValue; + }; + + editor.Initialized += (s, e) => + { + scrollBar.Size = int.Max (app.GetContentSize ().Height * 2, app.GetContentSize ().Width * 2); + editor.ViewToEdit = scrollBar; + }; + + } Application.Run (app); app.Dispose (); diff --git a/UICatalog/Scenarios/ScrollDemo.cs b/UICatalog/Scenarios/ScrollDemo.cs index 64a0ca85a8..fd96173f63 100644 --- a/UICatalog/Scenarios/ScrollDemo.cs +++ b/UICatalog/Scenarios/ScrollDemo.cs @@ -160,7 +160,7 @@ public override void Main () NumericUpDown scrollPosition = new () { - Value = scroll.Position, + Value = scroll.SliderPosition, X = Pos.Right (lblPosition) + 1, Y = Pos.Top (lblPosition) }; @@ -175,12 +175,12 @@ public override void Main () return; } - if (scroll.Position != e.NewValue) + if (scroll.SliderPosition != e.NewValue) { - scroll.Position = e.NewValue; + scroll.SliderPosition = e.NewValue; } - if (scroll.Position != e.NewValue) + if (scroll.SliderPosition != e.NewValue) { e.Cancel = true; } @@ -216,7 +216,7 @@ public override void Main () }; frameView.Add (lblPosChanging); - scroll.PositionChanging += (s, e) => { lblPosChanging.Text = $"PositionChanging event - CurrentValue: {e.CurrentValue}; NewValue: {e.NewValue}"; }; + scroll.SliderPositionChanging += (s, e) => { lblPosChanging.Text = $"PositionChanging event - CurrentValue: {e.CurrentValue}; NewValue: {e.NewValue}"; }; var lblPositionChanged = new Label { @@ -224,7 +224,7 @@ public override void Main () }; frameView.Add (lblPositionChanged); - scroll.PositionChanged += (s, e) => + scroll.SliderPositionChanged += (s, e) => { lblPositionChanged.Text = $"PositionChanged event - CurrentValue: {e.CurrentValue}"; scrollPosition.Value = e.CurrentValue; diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs index 488344f93b..e48e38d17d 100644 --- a/UnitTests/Views/ScrollBarTests.cs +++ b/UnitTests/Views/ScrollBarTests.cs @@ -179,12 +179,12 @@ string sizeHoriExpected _ = TestHelpers.AssertDriverContentsWithFrameAre (firstVertExpected, _output); - scrollBar.Position = 4; + scrollBar.SliderPosition = 4; Application.RunIteration (ref rs); _ = TestHelpers.AssertDriverContentsWithFrameAre (middleVertExpected, _output); - scrollBar.Position = 10; + scrollBar.SliderPosition = 10; Application.RunIteration (ref rs); _ = TestHelpers.AssertDriverContentsWithFrameAre (endVertExpected, _output); @@ -197,18 +197,18 @@ string sizeHoriExpected scrollBar.Orientation = Orientation.Horizontal; scrollBar.Width = 10; scrollBar.Height = 1; - scrollBar.Position = 0; + scrollBar.SliderPosition = 0; scrollBar.Size = size; Application.RunIteration (ref rs); _ = TestHelpers.AssertDriverContentsWithFrameAre (firstHoriExpected, _output); - scrollBar.Position = 4; + scrollBar.SliderPosition = 4; Application.RunIteration (ref rs); _ = TestHelpers.AssertDriverContentsWithFrameAre (middleHoriExpected, _output); - scrollBar.Position = 10; + scrollBar.SliderPosition = 10; Application.RunIteration (ref rs); _ = TestHelpers.AssertDriverContentsWithFrameAre (endHoriExpected, _output); @@ -226,7 +226,7 @@ public void Constructor_Defaults () Assert.False (scrollBar.CanFocus); Assert.Equal (Orientation.Vertical, scrollBar.Orientation); Assert.Equal (0, scrollBar.Size); - Assert.Equal (0, scrollBar.Position); + Assert.Equal (0, scrollBar.SliderPosition); Assert.Equal ("Auto(Content,Absolute(1),)", scrollBar.Width!.ToString ()); Assert.Equal ("Auto(Content,Absolute(1),)", scrollBar.Height!.ToString ()); //Assert.True (scrollBar.ShowScrollIndicator); @@ -241,7 +241,7 @@ public void KeepContentInAllViewport_True_False () 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 }; + scrollBar.SliderPositionChanged += (_, e) => view.Viewport = view.Viewport with { Y = e.CurrentValue }; view.Padding.Add (scrollBar); var top = new Toplevel (); top.Add (view); @@ -256,10 +256,10 @@ public void KeepContentInAllViewport_True_False () 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); + scrollBar.SliderPosition = 50; + Assert.Equal (scrollBar.SliderPosition, scrollBar.Size - 1); + Assert.Equal (scrollBar.SliderPosition, view.Viewport.Y); + Assert.Equal (29, scrollBar.SliderPosition); Assert.Equal (29, view.Viewport.Y); top.Dispose (); @@ -350,7 +350,7 @@ public void Mouse_On_The_Container_KeepContentInAllViewport_True (Orientation or Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Orientation = orientation, Size = size, - Position = position, + SliderPosition = position, KeepContentInAllViewport = true }; var top = new Toplevel (); @@ -366,7 +366,7 @@ public void Mouse_On_The_Container_KeepContentInAllViewport_True (Orientation or ScreenPosition = orientation == Orientation.Vertical ? new (0, location) : new Point (location, 0), Flags = MouseFlags.Button1Pressed }); - Assert.Equal (expectedPos, scrollBar.Position); + Assert.Equal (expectedPos, scrollBar.SliderPosition); Application.RunIteration (ref rs); _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); @@ -727,7 +727,7 @@ string expectedOut Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Orientation = orientation, - Size = size, Position = position, + Size = size, SliderPosition = position, KeepContentInAllViewport = true }; var top = new Toplevel (); @@ -774,7 +774,7 @@ string expectedOut Assert.Equal ("scrollSlider", Application.MouseGrabView?.Id); Assert.IsType (Application.MouseGrabView); - Assert.Equal (expectedPos, scrollBar.Position); + Assert.Equal (expectedPos, scrollBar.SliderPosition); Application.RunIteration (ref rs); _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); @@ -806,7 +806,7 @@ public void Mouse_Pressed_On_ScrollButton_Changes_Position_KeepContentInAllViewp var scroll = (Scroll)scrollBar.Subviews.FirstOrDefault (x => x is Scroll); Rectangle scrollSliderFrame = scroll!.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame; Assert.Equal (scrollSliderFrame, orientation == Orientation.Vertical ? new (0, 0, 1, 4) : new (0, 0, 4, 1)); - Assert.Equal (0, scrollBar.Position); + Assert.Equal (0, scrollBar.SliderPosition); Application.RunIteration (ref rs); @@ -822,11 +822,11 @@ public void Mouse_Pressed_On_ScrollButton_Changes_Position_KeepContentInAllViewp if (i < 10) { - Assert.Equal (i + 1, scrollBar.Position); + Assert.Equal (i + 1, scrollBar.SliderPosition); } else { - Assert.Equal (i, scrollBar.Position); + Assert.Equal (i, scrollBar.SliderPosition); Assert.Equal ( orientation == Orientation.Vertical ? new (0, 4) : new (4, 0), @@ -842,11 +842,11 @@ public void Mouse_Pressed_On_ScrollButton_Changes_Position_KeepContentInAllViewp if (i > 0) { - Assert.Equal (i - 1, scrollBar.Position); + Assert.Equal (i - 1, scrollBar.SliderPosition); } else { - Assert.Equal (0, scrollBar.Position); + Assert.Equal (0, scrollBar.SliderPosition); Assert.Equal (new (0, 0), scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); } } @@ -861,7 +861,7 @@ public void Moving_Mouse_Outside_Host_Ensures_Correct_Location_KeepContentInAllV var scrollBar = new ScrollBar { X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, - Position = 5, Orientation = orientation, KeepContentInAllViewport = true + SliderPosition = 5, Orientation = orientation, KeepContentInAllViewport = true }; var top = new Toplevel (); top.Add (scrollBar); @@ -902,19 +902,19 @@ public void Moving_Mouse_Outside_Host_Ensures_Correct_Location_KeepContentInAllV public void Position_Cannot_Be_Negative_Nor_Greater_Than_Size_Minus_Frame_Length_KeepContentInAllViewport_True (Orientation orientation, int size, int expectedPos1, int expectedPos2) { var scrollBar = new ScrollBar { Orientation = orientation, Height = 10, Size = size, KeepContentInAllViewport = true }; - Assert.Equal (0, scrollBar.Position); + Assert.Equal (0, scrollBar.SliderPosition); - scrollBar.Position = -1; + scrollBar.SliderPosition = -1; scrollBar.Layout (); - Assert.Equal (0, scrollBar.Position); + Assert.Equal (0, scrollBar.SliderPosition); - scrollBar.Position = size; + scrollBar.SliderPosition = size; scrollBar.Layout (); - Assert.Equal (expectedPos1, scrollBar.Position); + Assert.Equal (expectedPos1, scrollBar.SliderPosition); - scrollBar.Position = expectedPos2; + scrollBar.SliderPosition = expectedPos2; scrollBar.Layout (); - Assert.Equal (expectedPos2, scrollBar.Position); + Assert.Equal (expectedPos2, scrollBar.SliderPosition); } [Fact] @@ -924,7 +924,7 @@ public void PositionChanging_Cancelable_And_PositionChanged_Events () var changedCount = 0; var scrollBar = new ScrollBar { Size = 10 }; - scrollBar.PositionChanging += (s, e) => + scrollBar.SliderPositionChanging += (s, e) => { if (changingCount == 0) { @@ -933,16 +933,16 @@ public void PositionChanging_Cancelable_And_PositionChanged_Events () changingCount++; }; - scrollBar.PositionChanged += (s, e) => changedCount++; + scrollBar.SliderPositionChanged += (s, e) => changedCount++; - scrollBar.Position = 1; - Assert.Equal (0, scrollBar.Position); + scrollBar.SliderPosition = 1; + Assert.Equal (0, scrollBar.SliderPosition); Assert.Equal (1, changingCount); Assert.Equal (0, changedCount); - scrollBar.Position = 1; + scrollBar.SliderPosition = 1; scrollBar.Layout (); - Assert.Equal (1, scrollBar.Position); + Assert.Equal (1, scrollBar.SliderPosition); Assert.Equal (2, changingCount); Assert.Equal (1, changedCount); } @@ -954,73 +954,73 @@ public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position var cancel = false; var changed = 0; var scrollBar = new ScrollBar { Height = 10, Size = 20, KeepContentInAllViewport = true }; - scrollBar.PositionChanging += Scroll_PositionChanging; - scrollBar.PositionChanged += Scroll_PositionChanged; + scrollBar.SliderPositionChanging += Scroll_PositionChanging; + scrollBar.SliderPositionChanged += Scroll_PositionChanged; Assert.Equal (Orientation.Vertical, scrollBar.Orientation); scrollBar.Layout (); Assert.Equal (new (0, 0, 1, 10), scrollBar.Viewport); - Assert.Equal (0, scrollBar.Position); + Assert.Equal (0, scrollBar.SliderPosition); Assert.Equal (0, changing); Assert.Equal (0, changed); - scrollBar.Position = 0; + scrollBar.SliderPosition = 0; scrollBar.Layout (); - Assert.Equal (0, scrollBar.Position); + Assert.Equal (0, scrollBar.SliderPosition); Assert.Equal (0, changing); Assert.Equal (0, changed); - scrollBar.Position = 1; + scrollBar.SliderPosition = 1; scrollBar.Layout (); - Assert.Equal (1, scrollBar.Position); + Assert.Equal (1, scrollBar.SliderPosition); Assert.Equal (1, changing); Assert.Equal (1, changed); Reset (); cancel = true; - scrollBar.Position = 2; + scrollBar.SliderPosition = 2; scrollBar.Layout (); - Assert.Equal (1, scrollBar.Position); + Assert.Equal (1, scrollBar.SliderPosition); Assert.Equal (1, changing); Assert.Equal (0, changed); Reset (); - scrollBar.Position = 10; + scrollBar.SliderPosition = 10; scrollBar.Layout (); - Assert.Equal (10, scrollBar.Position); + Assert.Equal (10, scrollBar.SliderPosition); Assert.Equal (1, changing); Assert.Equal (1, changed); Reset (); - scrollBar.Position = 11; + scrollBar.SliderPosition = 11; scrollBar.Layout (); - Assert.Equal (10, scrollBar.Position); + Assert.Equal (10, scrollBar.SliderPosition); Assert.Equal (0, changing); Assert.Equal (0, changed); Reset (); - scrollBar.Position = 12; + scrollBar.SliderPosition = 12; scrollBar.Layout (); - Assert.Equal (10, scrollBar.Position); + Assert.Equal (10, scrollBar.SliderPosition); Assert.Equal (0, changing); Assert.Equal (0, changed); Reset (); - scrollBar.Position = 13; + scrollBar.SliderPosition = 13; scrollBar.Layout (); - Assert.Equal (10, scrollBar.Position); + Assert.Equal (10, scrollBar.SliderPosition); Assert.Equal (0, changing); Assert.Equal (0, changed); Reset (); - scrollBar.Position = 0; + scrollBar.SliderPosition = 0; scrollBar.Layout (); - Assert.Equal (0, scrollBar.Position); + Assert.Equal (0, scrollBar.SliderPosition); Assert.Equal (1, changing); Assert.Equal (1, changed); - scrollBar.PositionChanging -= Scroll_PositionChanging; - scrollBar.PositionChanged -= Scroll_PositionChanged; + scrollBar.SliderPositionChanging -= Scroll_PositionChanging; + scrollBar.SliderPositionChanged -= Scroll_PositionChanged; void Scroll_PositionChanging (object sender, CancelEventArgs e) { diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index ff5909f506..3fc8e7a805 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -34,10 +34,10 @@ public void OnOrientationChanged_Sets_Position_To_0 () }; super.Add (scroll); scroll.Layout (); - scroll.Position = 1; + scroll.SliderPosition = 1; scroll.Orientation = Orientation.Horizontal; - Assert.Equal (0, scroll.Position); + Assert.Equal (0, scroll.SliderPosition); } @@ -224,7 +224,7 @@ public void Constructor_Defaults () Assert.False (scroll.CanFocus); Assert.Equal (Orientation.Vertical, scroll.Orientation); Assert.Equal (0, scroll.Size); - Assert.Equal (0, scroll.Position); + Assert.Equal (0, scroll.SliderPosition); } //[Fact] @@ -343,7 +343,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Orientation = orientation, Size = size, - Position = position, + SliderPosition = position, }; var top = new Toplevel (); top.Add (scroll); @@ -358,7 +358,7 @@ public void Mouse_On_The_Container (Orientation orientation, int size, int posit ScreenPosition = orientation == Orientation.Vertical ? new (0, location) : new Point (location, 0), Flags = MouseFlags.Button1Pressed }); - Assert.Equal (expectedPos, scroll.Position); + Assert.Equal (expectedPos, scroll.SliderPosition); Application.RunIteration (ref rs); _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); @@ -719,7 +719,7 @@ string expectedOut Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Orientation = orientation, - Size = size, Position = position, + Size = size, SliderPosition = position, }; var top = new Toplevel (); top.Add (scroll); @@ -765,7 +765,7 @@ string expectedOut } Assert.Equal ("scrollSlider", Application.MouseGrabView?.Id); - Assert.Equal (expectedPos, scroll.Position); + Assert.Equal (expectedPos, scroll.SliderPosition); Application.RunIteration (ref rs); _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); @@ -788,7 +788,7 @@ public void Moving_Mouse_Outside_Host_Ensures_Correct_Location_KeepContentInAllV var scroll = new Scroll { X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, - Position = 5, Orientation = orientation + SliderPosition = 5, Orientation = orientation }; var top = new Toplevel (); top.Add (scroll); @@ -847,10 +847,10 @@ Orientation orientation scroll.Size = scrollSize; super.Layout (); - scroll.Position = scrollPosition; + scroll.SliderPosition = scrollPosition; super.Layout (); - Assert.True (scroll.Position <= scrollSize); + Assert.True (scroll.SliderPosition <= scrollSize); } [Fact] @@ -861,7 +861,7 @@ public void PositionChanging_Cancelable_And_PositionChanged_Events () var scroll = new Scroll { Size = 10 }; scroll.Layout (); - scroll.PositionChanging += (s, e) => + scroll.SliderPositionChanging += (s, e) => { if (changingCount == 0) { @@ -870,15 +870,15 @@ public void PositionChanging_Cancelable_And_PositionChanged_Events () changingCount++; }; - scroll.PositionChanged += (s, e) => changedCount++; + scroll.SliderPositionChanged += (s, e) => changedCount++; - scroll.Position = 1; - Assert.Equal (0, scroll.Position); + scroll.SliderPosition = 1; + Assert.Equal (0, scroll.SliderPosition); Assert.Equal (1, changingCount); Assert.Equal (0, changedCount); - scroll.Position = 1; - Assert.Equal (1, scroll.Position); + scroll.SliderPosition = 1; + Assert.Equal (1, scroll.SliderPosition); Assert.Equal (2, changingCount); Assert.Equal (1, changedCount); } @@ -890,53 +890,53 @@ public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position var cancel = false; var changed = 0; var scroll = new Scroll { Height = 10, Size = 20 }; - scroll.PositionChanging += Scroll_PositionChanging; - scroll.PositionChanged += Scroll_PositionChanged; + scroll.SliderPositionChanging += Scroll_PositionChanging; + scroll.SliderPositionChanged += Scroll_PositionChanged; Assert.Equal (Orientation.Vertical, scroll.Orientation); scroll.Layout (); Assert.Equal (new (0, 0, 1, 10), scroll.Viewport); - Assert.Equal (0, scroll.Position); + Assert.Equal (0, scroll.SliderPosition); Assert.Equal (0, changing); Assert.Equal (0, changed); - scroll.Position = 0; - Assert.Equal (0, scroll.Position); + scroll.SliderPosition = 0; + Assert.Equal (0, scroll.SliderPosition); Assert.Equal (0, changing); Assert.Equal (0, changed); - scroll.Position = 1; - Assert.Equal (1, scroll.Position); + scroll.SliderPosition = 1; + Assert.Equal (1, scroll.SliderPosition); Assert.Equal (1, changing); Assert.Equal (1, changed); Reset (); cancel = true; - scroll.Position = 2; - Assert.Equal (1, scroll.Position); + scroll.SliderPosition = 2; + Assert.Equal (1, scroll.SliderPosition); Assert.Equal (1, changing); Assert.Equal (0, changed); Reset (); - scroll.Position = 10; - Assert.Equal (10, scroll.Position); + scroll.SliderPosition = 10; + Assert.Equal (10, scroll.SliderPosition); Assert.Equal (1, changing); Assert.Equal (1, changed); Reset (); - scroll.Position = 11; - Assert.Equal (10, scroll.Position); + scroll.SliderPosition = 11; + Assert.Equal (10, scroll.SliderPosition); Assert.Equal (0, changing); Assert.Equal (0, changed); Reset (); - scroll.Position = 0; - Assert.Equal (0, scroll.Position); + scroll.SliderPosition = 0; + Assert.Equal (0, scroll.SliderPosition); Assert.Equal (1, changing); Assert.Equal (1, changed); - scroll.PositionChanging -= Scroll_PositionChanging; - scroll.PositionChanged -= Scroll_PositionChanged; + scroll.SliderPositionChanging -= Scroll_PositionChanging; + scroll.SliderPositionChanged -= Scroll_PositionChanged; void Scroll_PositionChanging (object sender, CancelEventArgs e) { @@ -1301,7 +1301,7 @@ public void Draws_Correctly (int superViewportWidth, int superViewportHeight, in scroll.Size = sliderSize; scroll.Layout (); - scroll.Position = sliderPosition; + scroll.SliderPosition = sliderPosition; super.BeginInit (); super.EndInit (); From b2eae4c90368aef5eb010f6b301a7df0ca594551 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 9 Nov 2024 19:25:12 -0700 Subject: [PATCH 081/128] Refactoring... WIP 3 --- Terminal.Gui/Views/Scroll/Scroll.cs | 176 +++++++++++++++---------- Terminal.Gui/Views/Scroll/ScrollBar.cs | 28 +++- UICatalog/Scenarios/CharacterMap.cs | 133 ++++++++++--------- 3 files changed, 201 insertions(+), 136 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 42a3da02ce..118e75e710 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -1,7 +1,6 @@ #nullable enable using System.ComponentModel; -using System.Drawing; namespace Terminal.Gui; @@ -39,8 +38,7 @@ public Scroll () OnOrientationChanged (Orientation); } - - /// + /// protected override void OnSubviewLayout (LayoutEventArgs args) { if (ViewportDimension < 1) @@ -49,10 +47,12 @@ protected override void OnSubviewLayout (LayoutEventArgs args) return; } - _slider.Size = (int)Math.Clamp (Math.Floor ((double)ViewportDimension * ViewportDimension / (Size)), 1, ViewportDimension); + + _slider.Size = (int)Math.Clamp (Math.Floor ((double)ViewportDimension * ViewportDimension / (Size - 2)), 1, ViewportDimension); } #region IOrientation members + private readonly OrientationHelper _orientationHelper; /// @@ -133,16 +133,16 @@ protected virtual void OnSizeChanged (int size) { } /// Raised when has changed. public event EventHandler>? SizeChanged; - private int _sliderPosition; - + #region SliderPosition private void OnSliderOnFrameChanged (object? sender, EventArgs args) { if (ViewportDimension == 0) { return; } + int framePos = Orientation == Orientation.Vertical ? args.CurrentValue.Y : args.CurrentValue.X; - RaisePositionChangeEvents (_sliderPosition, framePos); + SliderPosition = framePos; } /// @@ -150,57 +150,23 @@ private void OnSliderOnFrameChanged (object? sender, EventArgs args) /// public int SliderPosition { - get => _sliderPosition; - set => RaisePositionChangeEvents (_sliderPosition, value); + get => CalculateSliderPosition (_contentPosition); + set => RaiseSliderPositionChangeEvents (value); } - /// - /// Gets or sets the position of the ScrollSlider within the range of 0.... - /// - public int ContentPosition + private void RaiseSliderPositionChangeEvents (int newSliderPosition) { - get - { - if (ViewportDimension - _slider.Size == 0) - { - return 0; - } - return (int)Math.Round (GetCurrentContentPosition ()); - } - set - { - if (Size - ViewportDimension == 0) - { - SliderPosition = 0; - return; - } - - double newContentPos = (double)value / (ViewportDimension - _slider.Size) * (Size - ViewportDimension); - double newSliderPos = (double)value / (Size - ViewportDimension) * (ViewportDimension - _slider.Size); - - int newSliderPosition = (int)Math.Ceiling (newSliderPos); - if (newContentPos >= GetCurrentContentPosition ()) - { - newSliderPosition = (int)Math.Floor (newSliderPos); - } + int currentSliderPosition = CalculateSliderPosition (_contentPosition); - if (SliderPosition != newSliderPosition) - { - SliderPosition = newSliderPosition; - } + if (newSliderPosition > Size - ViewportDimension) + { + return; } - } - - private double GetCurrentContentPosition () - { - return (double)SliderPosition / (ViewportDimension - _slider.Size) * (Size - ViewportDimension); - } - private void RaisePositionChangeEvents (int currentSliderPosition, int newSliderPosition) - { - if (OnPositionChanging (currentSliderPosition, newSliderPosition)) + if (OnSliderPositionChanging (currentSliderPosition, newSliderPosition)) { _slider.Position = currentSliderPosition; + return; } @@ -210,30 +176,28 @@ private void RaisePositionChangeEvents (int currentSliderPosition, int newSlider if (args.Cancel) { _slider.Position = currentSliderPosition; + return; } // This sets the slider position and clamps the value _slider.Position = newSliderPosition; - if (_slider.Position == _sliderPosition) + if (_slider.Position == currentSliderPosition) { return; } - _sliderPosition = newSliderPosition; + ContentPosition = (int)Math.Round ((double)newSliderPosition / (ViewportDimension - _slider.Size) * (Size - ViewportDimension)); - OnPositionChanged (_sliderPosition); + OnSliderPositionChanged (newSliderPosition); SliderPositionChanged?.Invoke (this, new (in newSliderPosition)); } /// /// Called when is changing. Return true to cancel the change. /// - protected virtual bool OnPositionChanging (int currentPos, int newPos) - { - return false; - } + protected virtual bool OnSliderPositionChanging (int currentSliderPosition, int newSliderPosition) { return false; } /// /// Raised when the is changing. Set to @@ -242,11 +206,88 @@ protected virtual bool OnPositionChanging (int currentPos, int newPos) public event EventHandler>? SliderPositionChanging; /// Called when has changed. - protected virtual void OnPositionChanged (int position) { } + protected virtual void OnSliderPositionChanged (int position) { } /// Raised when the has changed. public event EventHandler>? SliderPositionChanged; + private int CalculateSliderPosition (int contentPosition) + { + if (Size - ViewportDimension == 0) + { + return 0; + } + + return (int)Math.Round ((double)contentPosition / (Size - ViewportDimension) * (ViewportDimension - _slider.Size)); + } + + #endregion SliderPosition + + #region ContentPosition + + private int _contentPosition; + + /// + /// Gets or sets the position of the ScrollSlider within the range of 0.... + /// + public int ContentPosition + { + get => _contentPosition; + set + { + if (value == _contentPosition) + { + return; + } + + RaiseContentPositionChangeEvents (value); + } + } + + private void RaiseContentPositionChangeEvents (int newContentPosition) + { + // Clamp the value between 0 and Size - ViewportDimension + newContentPosition = (int)Math.Clamp (newContentPosition, 0, Size - ViewportDimension); + + if (OnContentPositionChanging (_contentPosition, newContentPosition)) + { + return; + } + + CancelEventArgs args = new (ref _contentPosition, ref newContentPosition); + ContentPositionChanging?.Invoke (this, args); + + if (args.Cancel) + { + return; + } + + _contentPosition = newContentPosition; + + SliderPosition = CalculateSliderPosition (_contentPosition); + + OnContentPositionChanged (_contentPosition); + ContentPositionChanged?.Invoke (this, new (in _contentPosition)); + } + + /// + /// Called when is changing. Return true to cancel the change. + /// + protected virtual bool OnContentPositionChanging (int currentPos, int newPos) { return false; } + + /// + /// Raised when the is changing. Set to + /// to prevent the position from being changed. + /// + public event EventHandler>? ContentPositionChanging; + + /// Called when has changed. + protected virtual void OnContentPositionChanged (int position) { } + + /// Raised when the has changed. + public event EventHandler>? ContentPositionChanged; + + #endregion ContentPosition /// protected override bool OnClearingViewport () @@ -256,7 +297,7 @@ protected override bool OnClearingViewport () return true; } - /// + /// protected override bool OnMouseClick (MouseEventArgs args) { if (!args.IsSingleClicked) @@ -285,7 +326,6 @@ protected override bool OnMouseClick (MouseEventArgs args) SliderPosition = args.Position.X; } - return true; } @@ -306,29 +346,31 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledDown)) { - SliderPosition++; + ContentPosition++; } + if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledUp)) { - SliderPosition--; + ContentPosition--; } } else { if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledRight)) { - SliderPosition++; + ContentPosition++; } + if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledLeft)) { - SliderPosition--; + ContentPosition--; } } return true; } - /// + /// public bool EnableForDesign () { OrientationChanged += (sender, args) => @@ -348,9 +390,7 @@ public bool EnableForDesign () Width = 1; Height = Dim.Fill (); Size = 1000; - SliderPosition = 10; - - + ContentPosition = 10; return true; } diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index 334cd5cf2f..1ee3abc014 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -29,6 +29,8 @@ public ScrollBar () _scroll = new (); _scroll.SliderPositionChanging += OnScrollOnSliderPositionChanging; _scroll.SliderPositionChanged += OnScrollOnSliderPositionChanged; + _scroll.ContentPositionChanging += OnScrollOnContentPositionChanging; + _scroll.ContentPositionChanged += OnScrollOnContentPositionChanged; _scroll.SizeChanged += OnScrollOnSizeChanged; _decreaseButton = new () @@ -64,13 +66,13 @@ public ScrollBar () void OnDecreaseButtonOnAccept (object? s, CommandEventArgs e) { - _scroll.ContentPosition--; + ContentPosition -= Increment; e.Cancel = true; } void OnIncreaseButtonOnAccept (object? s, CommandEventArgs e) { - _scroll.ContentPosition++; + ContentPosition += Increment; e.Cancel = true; } } @@ -188,8 +190,8 @@ public int SliderPosition set => _scroll.SliderPosition = value; } - private void OnScrollOnSliderPositionChanged (object? sender, EventArgs e) { SliderPositionChanged?.Invoke (this, e); } private void OnScrollOnSliderPositionChanging (object? sender, CancelEventArgs e) { SliderPositionChanging?.Invoke (this, e); } + private void OnScrollOnSliderPositionChanged (object? sender, EventArgs e) { SliderPositionChanged?.Invoke (this, e); } /// /// Raised when the is changing. Set to @@ -219,6 +221,18 @@ public int ContentPosition set => _scroll.ContentPosition = value; } + private void OnScrollOnContentPositionChanging (object? sender, CancelEventArgs e) { ContentPositionChanging?.Invoke (this, e); } + private void OnScrollOnContentPositionChanged (object? sender, EventArgs e) { ContentPositionChanged?.Invoke (this, e); } + + /// + /// Raised when the is changing. Set to + /// to prevent the position from being changed. + /// + public event EventHandler>? ContentPositionChanging; + + /// Raised when the has changed. + public event EventHandler>? ContentPositionChanged; + /// Raised when has changed. public event EventHandler>? SizeChanged; @@ -228,6 +242,14 @@ private void OnScrollOnSizeChanged (object? sender, EventArgs e) SizeChanged?.Invoke (this, e); } + /// + /// Gets or sets the amount each click of the increment/decrement buttons will incremenet/decrement the . + /// + /// + /// The default is 1. + /// + public int Increment { get; set; } = 1; + /// protected override void OnSubviewLayout (LayoutEventArgs args) { PositionSubviews (); } diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index f069ca457d..5f513f3d66 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -10,9 +10,7 @@ using System.Text.Json; using System.Text.Unicode; using System.Threading.Tasks; -using JetBrains.Annotations; using Terminal.Gui; -using static System.Runtime.InteropServices.JavaScript.JSType; using static Terminal.Gui.SpinnerStyle; namespace UICatalog.Scenarios; @@ -303,7 +301,7 @@ private MenuItem CreateMenuShowWidth () public override List GetDemoKeyStrokes () { - List keys = new List (); + List keys = new (); for (var i = 0; i < 200; i++) { @@ -345,15 +343,17 @@ public CharMap () ColorScheme = Colors.ColorSchemes ["Dialog"]; CanFocus = true; CursorVisibility = CursorVisibility.Default; + //ViewportSettings = ViewportSettings.AllowLocationGreaterThanContentSize; - SetContentSize (new (COLUMN_WIDTH * 16, (_maxCodePoint / 16) * _rowHeight)); // +1 for Header + SetContentSize (new (COLUMN_WIDTH * 16 + RowLabelWidth, _maxCodePoint / 16 * _rowHeight)); // +1 for Header AddCommand ( Command.Up, () => { SelectedCodePoint -= 16; + return true; } ); @@ -363,6 +363,7 @@ public CharMap () () => { SelectedCodePoint += 16; + return true; } ); @@ -372,6 +373,7 @@ public CharMap () () => { SelectedCodePoint--; + return true; } ); @@ -380,7 +382,8 @@ public CharMap () Command.Right, () => { - SelectedCodePoint++; + SelectedCodePoint++; + return true; } ); @@ -455,11 +458,12 @@ public CharMap () _hScrollBar = new () { AutoHide = false, - X = RowLabelWidth + 1, + X = RowLabelWidth, Y = Pos.AnchorEnd (), Orientation = Orientation.Horizontal, Width = Dim.Fill (1), - Size = GetContentSize ().Width + Size = GetContentSize ().Width - RowLabelWidth, + Increment = COLUMN_WIDTH }; _vScrollBar = new () @@ -473,26 +477,31 @@ public CharMap () Padding.Add (_vScrollBar, _hScrollBar); - _vScrollBar.SliderPositionChanged += (sender, args) => - { - if (Viewport.Height > 0) - { - Viewport = Viewport with { Y = _vScrollBar.ContentPosition }; - } - }; - - _hScrollBar.SliderPositionChanged += (sender, args) => - { - if (Viewport.Width > 0) - { - Viewport = Viewport with { X = _hScrollBar.ContentPosition }; - } - }; + _vScrollBar.ContentPositionChanged += (sender, args) => + { + if (Viewport.Height > 0) + { + Viewport = Viewport with + { + Y = Math.Min (args.CurrentValue, GetContentSize ().Height - (Viewport.Height - 2)) + }; + } + }; + + _hScrollBar.ContentPositionChanged += (sender, args) => + { + if (Viewport.Width > 0) + { + Viewport = Viewport with + { + X = Math.Min (args.CurrentValue, GetContentSize ().Width - Viewport.Width) + }; + } + }; FrameChanged += (sender, args) => { - int width = Viewport.Width / COLUMN_WIDTH * COLUMN_WIDTH - RowLabelWidth; - if (width < GetContentSize ().Width) + if (Viewport.Width < GetContentSize ().Width) { Padding.Thickness = Padding.Thickness with { Bottom = 1 }; } @@ -500,6 +509,7 @@ public CharMap () { Padding.Thickness = Padding.Thickness with { Bottom = 0 }; } + _hScrollBar.ContentPosition = Viewport.X; _vScrollBar.ContentPosition = Viewport.Y; }; @@ -527,7 +537,8 @@ private void Handle_MouseEvent (object sender, MouseEventArgs e) if (e.Flags == MouseFlags.WheeledRight) { - _hScrollBar.SliderPosition++; + ScrollHorizontal (1); + _hScrollBar.ContentPosition = Viewport.X; e.Handled = true; return; @@ -535,7 +546,8 @@ private void Handle_MouseEvent (object sender, MouseEventArgs e) if (e.Flags == MouseFlags.WheeledLeft) { - _hScrollBar.SliderPosition--; + ScrollHorizontal (-1); + _hScrollBar.ContentPosition = Viewport.X; e.Handled = true; } } @@ -545,8 +557,8 @@ public Point Cursor { get { - int row = SelectedCodePoint / 16 * _rowHeight - Viewport.Y + 1; // + 1 for header - int col = SelectedCodePoint % 16 * COLUMN_WIDTH - Viewport.X + RowLabelWidth + 1; // + 1 for padding between label and first column + int row = SelectedCodePoint / 16 * _rowHeight + 1 - Viewport.Y; // + 1 for header + int col = SelectedCodePoint % 16 * COLUMN_WIDTH + RowLabelWidth + 1 - Viewport.X; // + 1 for padding between label and first column return new (col, row); } @@ -556,7 +568,8 @@ public Point Cursor public static int _maxCodePoint = UnicodeRange.Ranges.Max (r => r.End); /// - /// Gets or sets the currently selected codepoint. Causes the Viewport to scroll to make the selected code point visible. + /// Gets or sets the currently selected codepoint. Causes the Viewport to scroll to make the selected code point + /// visible. /// public int SelectedCodePoint { @@ -571,57 +584,47 @@ public int SelectedCodePoint Point prevCursor = Cursor; int newSelectedCodePoint = Math.Clamp (value, 0, _maxCodePoint); - ScrollToMakeRowVisible (newSelectedCodePoint / 16 * _rowHeight, prevCursor.Y); - ScrollToMakeColumnVisible (newSelectedCodePoint % 16 * COLUMN_WIDTH, prevCursor.X); - - if (_selected != newSelectedCodePoint) + Point newCursor = new () { - _selected = newSelectedCodePoint; - SetNeedsDraw (); - SelectedCodePointChanged?.Invoke (this, new (SelectedCodePoint, null)); - } + X = newSelectedCodePoint % 16 * COLUMN_WIDTH + RowLabelWidth + 1 - Viewport.X, + Y = newSelectedCodePoint / 16 * _rowHeight + 1 - Viewport.Y + }; + + // Ensure the new cursor position is visible + EnsureCursorIsVisible (newCursor); + + _selected = newSelectedCodePoint; + SetNeedsDraw (); + SelectedCodePointChanged?.Invoke (this, new (SelectedCodePoint, null)); } } - private void ScrollToMakeRowVisible (int row, int prevCursorY) + private void EnsureCursorIsVisible (Point newCursor) { - int height = Viewport.Height - 1; // Header - int delta = row - (Viewport.Y + height); - int scroll = Viewport.Height - (prevCursorY - delta); - - if (row - Viewport.Y < 0) + // Adjust vertical scrolling + if (newCursor.Y < 1) // Header is at Y = 0 { - // Moving up. - Viewport = Viewport with { Y = Viewport.Y + (row - Viewport.Y) }; + ScrollVertical (newCursor.Y - 1); } - else if (row - Viewport.Y >= height) + else if (newCursor.Y >= Viewport.Height) { - // Moving down. - Viewport = Viewport with { Y = Math.Min (Viewport.Y + scroll, GetContentSize ().Height - height ) }; + ScrollVertical (newCursor.Y - Viewport.Height + 1); } - _vScrollBar.ContentPosition = row; - } - private void ScrollToMakeColumnVisible (int col, int prevCursorX) - { - int width = Viewport.Width - RowLabelWidth; - int delta = col - (Viewport.X + width); - int scroll = Viewport.Width - (prevCursorX - delta); + _vScrollBar.ContentPosition = Viewport.Y; - if (col - Viewport.X < 0) + // Adjust horizontal scrolling + if (newCursor.X < RowLabelWidth + 1) { - // Moving left. - Viewport = Viewport with { X = Viewport.X + (col - Viewport.X) }; - _hScrollBar.ContentPosition = col; + ScrollHorizontal (newCursor.X - (RowLabelWidth + 1)); } - else if (col - Viewport.X >= width) + else if (newCursor.X >= Viewport.Width) { - // Moving right. - Viewport = Viewport with { X = Math.Min (Viewport.X + scroll, GetContentSize ().Width - width) }; - _hScrollBar.ContentPosition = col; + ScrollHorizontal (newCursor.X - Viewport.Width + 1); } - } + _hScrollBar.ContentPosition = Viewport.X; + } public bool ShowGlyphWidths { @@ -997,7 +1000,7 @@ private void ShowDetails () document.RootElement, new JsonSerializerOptions - { WriteIndented = true } + { WriteIndented = true } ); } From 701d59291324e7d292641df132a2009d63b04545 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 9 Nov 2024 22:39:47 -0700 Subject: [PATCH 082/128] Unit tests pass --- Terminal.Gui/Views/Scroll/Scroll.cs | 32 +- Terminal.Gui/Views/Scroll/ScrollBar.cs | 7 +- Terminal.Gui/Views/Scroll/ScrollSlider.cs | 5 + UnitTests/Views/ScrollBarTests.cs | 1185 ++++----------------- UnitTests/Views/ScrollSliderTests.cs | 22 + UnitTests/Views/ScrollTests.cs | 942 +++------------- 6 files changed, 411 insertions(+), 1782 deletions(-) diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index 118e75e710..e240d5cdb2 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -48,7 +48,7 @@ protected override void OnSubviewLayout (LayoutEventArgs args) return; } - _slider.Size = (int)Math.Clamp (Math.Floor ((double)ViewportDimension * ViewportDimension / (Size - 2)), 1, ViewportDimension); + _slider.Size = (int)Math.Clamp (Math.Floor ((double)ViewportDimension * ViewportDimension / (Size)), 1, ViewportDimension); } #region IOrientation members @@ -124,6 +124,7 @@ public int Size _size = value; OnSizeChanged (_size); SizeChanged?.Invoke (this, new (in _size)); + SetNeedsLayout (); } } @@ -158,15 +159,13 @@ private void RaiseSliderPositionChangeEvents (int newSliderPosition) { int currentSliderPosition = CalculateSliderPosition (_contentPosition); - if (newSliderPosition > Size - ViewportDimension) + if (newSliderPosition > Size - ViewportDimension || currentSliderPosition == newSliderPosition) { return; } if (OnSliderPositionChanging (currentSliderPosition, newSliderPosition)) { - _slider.Position = currentSliderPosition; - return; } @@ -175,19 +174,12 @@ private void RaiseSliderPositionChangeEvents (int newSliderPosition) if (args.Cancel) { - _slider.Position = currentSliderPosition; - return; } // This sets the slider position and clamps the value _slider.Position = newSliderPosition; - if (_slider.Position == currentSliderPosition) - { - return; - } - ContentPosition = (int)Math.Round ((double)newSliderPosition / (ViewportDimension - _slider.Size) * (Size - ViewportDimension)); OnSliderPositionChanged (newSliderPosition); @@ -247,7 +239,7 @@ public int ContentPosition private void RaiseContentPositionChangeEvents (int newContentPosition) { // Clamp the value between 0 and Size - ViewportDimension - newContentPosition = (int)Math.Clamp (newContentPosition, 0, Size - ViewportDimension); + newContentPosition = (int)Math.Clamp (newContentPosition, 0, Math.Max (0, Size - ViewportDimension)); if (OnContentPositionChanging (_contentPosition, newContentPosition)) { @@ -329,6 +321,14 @@ protected override bool OnMouseClick (MouseEventArgs args) return true; } + /// + /// Gets or sets the amount each mouse hweel event will incremenet/decrement the . + /// + /// + /// The default is 1. + /// + public int Increment { get; set; } = 1; + /// protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { @@ -346,24 +346,24 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledDown)) { - ContentPosition++; + ContentPosition += Increment; } if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledUp)) { - ContentPosition--; + ContentPosition -= Increment; } } else { if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledRight)) { - ContentPosition++; + ContentPosition += Increment; } if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledLeft)) { - ContentPosition--; + ContentPosition -= Increment; } } diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs index 1ee3abc014..7bf0e1ea68 100644 --- a/Terminal.Gui/Views/Scroll/ScrollBar.cs +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -243,12 +243,13 @@ private void OnScrollOnSizeChanged (object? sender, EventArgs e) } /// - /// Gets or sets the amount each click of the increment/decrement buttons will incremenet/decrement the . + /// Gets or sets the amount each click of the increment/decrement buttons and each + /// mouse wheel event will incremenet/decrement the . /// /// /// The default is 1. /// - public int Increment { get; set; } = 1; + public int Increment { get => _scroll.Increment; set => _scroll.Increment = value; } /// protected override void OnSubviewLayout (LayoutEventArgs args) { PositionSubviews (); } @@ -285,7 +286,7 @@ private void PositionSubviews () _increaseButton.Height = Dim.Fill (); _increaseButton.Title = Glyphs.RightArrow.ToString (); _scroll.Y = 0; - _scroll.X = Pos.Bottom (_decreaseButton); + _scroll.X = Pos.Right (_decreaseButton); _scroll.Width = Dim.Fill (1); _scroll.Height = Dim.Fill (); } diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs index 5d8f01ffe8..a3e58beddd 100644 --- a/Terminal.Gui/Views/Scroll/ScrollSlider.cs +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -190,6 +190,11 @@ protected override bool OnDrawingText () return false; } + if (SuperView is null) + { + return false; + } + if (Orientation == Orientation.Vertical) { Text = $"{(int)Math.Round ((double)Viewport.Height / SuperView!.GetContentSize ().Height * 100)}%"; diff --git a/UnitTests/Views/ScrollBarTests.cs b/UnitTests/Views/ScrollBarTests.cs index e48e38d17d..460ea18bff 100644 --- a/UnitTests/Views/ScrollBarTests.cs +++ b/UnitTests/Views/ScrollBarTests.cs @@ -1,4 +1,5 @@ using Xunit.Abstractions; +using static Unix.Terminal.Delegates; namespace Terminal.Gui.ViewsTests; @@ -43,197 +44,119 @@ public void AutoHideScrollBar_CheckScrollBarVisibility () scrollBarSuperView.SuperView!.Dispose (); } - [Theory] - [AutoInitShutdown] - [InlineData ( - 20, - @" -▲ -█ -█ -█ -█ -░ -░ -░ -░ -▼", - @" -▲ -░ -░ -█ -█ -█ -█ -░ -░ -▼", - @" -▲ -░ -░ -░ -░ -█ -█ -█ -█ -▼", - @" -▲ -░ -░ -█ -█ -░ -░ -░ -░ -▼", - @" -◄████░░░░►", - @" -◄░░████░░►", - @" -◄░░░░████►", - @" -◄░░██░░░░►")] - [InlineData ( - 40, - @" -▲ -█ -█ -░ -░ -░ -░ -░ -░ -▼", - @" -▲ -░ -█ -█ -░ -░ -░ -░ -░ -▼", - @" -▲ -░ -░ -█ -█ -░ -░ -░ -░ -▼", - @" -▲ -░ -█ -░ -░ -░ -░ -░ -░ -▼", - @" -◄██░░░░░░►", - @" -◄░██░░░░░►", - @" -◄░░██░░░░►", - @" -◄░█░░░░░░►")] - public void Changing_Position_Size_Orientation_Draws_Correctly_KeepContentInAllViewport_True ( - int size, - string firstVertExpected, - string middleVertExpected, - string endVertExpected, - string sizeVertExpected, - string firstHoriExpected, - string middleHoriExpected, - string endHoriExpected, - string sizeHoriExpected - ) - { - var scrollBar = new ScrollBar - { - Orientation = Orientation.Vertical, - Size = size, - Height = 10, - KeepContentInAllViewport = true - }; - var top = new Toplevel (); - top.Add (scrollBar); - RunState rs = Application.Begin (top); - Application.RunIteration (ref rs); - - _ = TestHelpers.AssertDriverContentsWithFrameAre (firstVertExpected, _output); - scrollBar.SliderPosition = 4; - Application.RunIteration (ref rs); - - _ = TestHelpers.AssertDriverContentsWithFrameAre (middleVertExpected, _output); - - scrollBar.SliderPosition = 10; - Application.RunIteration (ref rs); + [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.SliderPosition); + Assert.True (scrollBar.AutoHide); + } - _ = TestHelpers.AssertDriverContentsWithFrameAre (endVertExpected, _output); - scrollBar.Size = size * 2; - Application.RunIteration (ref rs); + [Fact] + public void OnOrientationChanged_Keeps_Size () + { + var scroll = new Scroll (); + scroll.Layout (); + scroll.Size = 1; - _ = TestHelpers.AssertDriverContentsWithFrameAre (sizeVertExpected, _output); + scroll.Orientation = Orientation.Horizontal; + Assert.Equal (1, scroll.Size); + } + [Fact] + public void OnOrientationChanged_Sets_Position_To_0 () + { + View super = new View () + { + Id = "super", + Width = 10, + Height = 10 + }; + var scrollBar = new ScrollBar () + { + }; + super.Add (scrollBar); + scrollBar.Layout (); + scrollBar.SliderPosition = 1; scrollBar.Orientation = Orientation.Horizontal; - scrollBar.Width = 10; - scrollBar.Height = 1; - scrollBar.SliderPosition = 0; - scrollBar.Size = size; - Application.RunIteration (ref rs); - _ = TestHelpers.AssertDriverContentsWithFrameAre (firstHoriExpected, _output); + Assert.Equal (0, scrollBar.SliderPosition); + } - scrollBar.SliderPosition = 4; - Application.RunIteration (ref rs); - _ = TestHelpers.AssertDriverContentsWithFrameAre (middleHoriExpected, _output); + [Fact] + public void SliderPosition_Event_Cancelables () + { + var changingCount = 0; + var changedCount = 0; + var scrollBar = new ScrollBar { }; + scrollBar.Layout (); + scrollBar.Size = scrollBar.Viewport.Height * 2; + scrollBar.Layout (); - scrollBar.SliderPosition = 10; - Application.RunIteration (ref rs); + scrollBar.SliderPositionChanging += (s, e) => + { + if (changingCount == 0) + { + e.Cancel = true; + } - _ = TestHelpers.AssertDriverContentsWithFrameAre (endHoriExpected, _output); + changingCount++; + }; + scrollBar.SliderPositionChanged += (s, e) => changedCount++; - scrollBar.Size = size * 2; - Application.RunIteration (ref rs); + scrollBar.SliderPosition = 1; + Assert.Equal (0, scrollBar.SliderPosition); + Assert.Equal (1, changingCount); + Assert.Equal (0, changedCount); - _ = TestHelpers.AssertDriverContentsWithFrameAre (sizeHoriExpected, _output); + scrollBar.SliderPosition = 1; + Assert.Equal (1, scrollBar.SliderPosition); + Assert.Equal (2, changingCount); + Assert.Equal (1, changedCount); } + [Fact] - public void Constructor_Defaults () + public void ContentPosition_Event_Cancelables () { - var scrollBar = new ScrollBar (); - Assert.False (scrollBar.CanFocus); - Assert.Equal (Orientation.Vertical, scrollBar.Orientation); - Assert.Equal (0, scrollBar.Size); - Assert.Equal (0, scrollBar.SliderPosition); - Assert.Equal ("Auto(Content,Absolute(1),)", scrollBar.Width!.ToString ()); - Assert.Equal ("Auto(Content,Absolute(1),)", scrollBar.Height!.ToString ()); - //Assert.True (scrollBar.ShowScrollIndicator); - Assert.True (scrollBar.AutoHide); + var changingCount = 0; + var changedCount = 0; + var scrollBar = new ScrollBar { }; + scrollBar.Layout (); + scrollBar.Size = scrollBar.Viewport.Height * 2; + scrollBar.Layout (); + + scrollBar.ContentPositionChanging += (s, e) => + { + if (changingCount == 0) + { + e.Cancel = true; + } + + changingCount++; + }; + scrollBar.ContentPositionChanged += (s, e) => changedCount++; + + scrollBar.ContentPosition = 1; + Assert.Equal (0, scrollBar.ContentPosition); + Assert.Equal (1, changingCount); + Assert.Equal (0, changedCount); + + scrollBar.ContentPosition = 1; + Assert.Equal (1, scrollBar.ContentPosition); + Assert.Equal (2, changingCount); + Assert.Equal (1, changedCount); } - [Fact] + + + [Fact (Skip = "Disabled - Will put this feature in View")] [AutoInitShutdown] public void KeepContentInAllViewport_True_False () { @@ -265,594 +188,8 @@ public void KeepContentInAllViewport_True_False () top.Dispose (); } - [Theory] - [AutoInitShutdown] - [InlineData ( - Orientation.Vertical, - 20, - 10, - 4, - @" -▲ -░ -░ -░ -░ -█ -█ -█ -█ -▼", - 2, - @" -▲ -░ -█ -█ -█ -█ -░ -░ -░ -▼")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 5, - @" -▲ -░ -░ -█ -█ -░ -░ -░ -░ -▼", - 18, - @" -▲ -░ -░ -░ -░ -█ -█ -░ -░ -▼")] - [InlineData ( - Orientation.Horizontal, - 20, - 10, - 4, - @" -◄░░░░████►", - 2, - @" -◄░████░░░►")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 5, - @" -◄░░██░░░░►", - 18, - @" -◄░░░░██░░►")] - public void Mouse_On_The_Container_KeepContentInAllViewport_True (Orientation orientation, int size, int position, int location, string output, int expectedPos, string expectedOut) - { - var scrollBar = new ScrollBar - { - Width = orientation == Orientation.Vertical ? 1 : 10, - Height = orientation == Orientation.Vertical ? 10 : 1, - Orientation = orientation, Size = size, - SliderPosition = position, - KeepContentInAllViewport = true - }; - var top = new Toplevel (); - top.Add (scrollBar); - RunState rs = Application.Begin (top); - Application.RunIteration (ref rs); - - _ = TestHelpers.AssertDriverContentsWithFrameAre (output, _output); - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (0, location) : new Point (location, 0), - Flags = MouseFlags.Button1Pressed - }); - Assert.Equal (expectedPos, scrollBar.SliderPosition); - - Application.RunIteration (ref rs); - _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); - } - - [Theory] - [AutoInitShutdown] - [InlineData ( - Orientation.Vertical, - 20, - 10, - 5, - 5, - @" -▲ -░ -░ -░ -░ -█ -█ -█ -█ -▼", - MouseFlags.Button1Pressed, - 10, - @" -▲ -░ -░ -░ -░ -█ -█ -█ -█ -▼")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 3, - 3, - @" -▲ -░ -░ -█ -█ -░ -░ -░ -░ -▼", - MouseFlags.Button1Pressed, - 10, - @" -▲ -░ -░ -█ -█ -░ -░ -░ -░ -▼")] - [InlineData ( - Orientation.Horizontal, - 20, - 10, - 5, - 5, - @" -◄░░░░████►", - MouseFlags.Button1Pressed, - 10, - @" -◄░░░░████►")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 3, - 3, - @" -◄░░██░░░░►", - MouseFlags.Button1Pressed, - 10, - @" -◄░░██░░░░►")] - [InlineData ( - Orientation.Vertical, - 20, - 10, - 5, - 7, - @" -▲ -░ -░ -░ -░ -█ -█ -█ -█ -▼", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 10, - @" -▲ -░ -░ -░ -░ -█ -█ -█ -█ -▼")] - [InlineData ( - Orientation.Horizontal, - 20, - 10, - 5, - 4, - @" -◄░░░░████►", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 8, - @" -◄░░░████░►")] - [InlineData ( - Orientation.Vertical, - 20, - 10, - 5, - 6, - @" -▲ -░ -░ -░ -░ -█ -█ -█ -█ -▼", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 10, - @" -▲ -░ -░ -░ -░ -█ -█ -█ -█ -▼")] - [InlineData ( - Orientation.Horizontal, - 20, - 10, - 5, - 6, - @" -◄░░░░████►", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 10, - @" -◄░░░░████►")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 2, - 1, - @" -▲ -░ -░ -█ -█ -░ -░ -░ -░ -▼", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 2, - @" -▲ -█ -█ -░ -░ -░ -░ -░ -░ -▼")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 2, - 1, - @" -◄░░██░░░░►", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 2, - @" -◄██░░░░░░►")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 3, - 4, - @" -▲ -░ -░ -█ -█ -░ -░ -░ -░ -▼", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 15, - @" -▲ -░ -░ -░ -█ -█ -░ -░ -░ -▼")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 3, - 4, - @" -◄░░██░░░░►", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 15, - @" -◄░░░██░░░►")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 3, - 3, - @" -▲ -░ -░ -█ -█ -░ -░ -░ -░ -▼", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 10, - @" -▲ -░ -░ -█ -█ -░ -░ -░ -░ -▼")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 3, - 3, - @" -◄░░██░░░░►", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 10, - @" -◄░░██░░░░►")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 3, - 5, - @" -▲ -░ -░ -█ -█ -░ -░ -░ -░ -▼", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 20, - @" -▲ -░ -░ -░ -░ -█ -█ -░ -░ -▼")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 3, - 5, - @" -◄░░██░░░░►", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 20, - @" -◄░░░░██░░►")] - public void Mouse_On_The_Slider_KeepContentInAllViewport_True ( - Orientation orientation, - int size, - int position, - int startLocation, - int endLocation, - string output, - MouseFlags mouseFlags, - int expectedPos, - string expectedOut - ) - { - var scrollBar = new ScrollBar - { - Width = orientation == Orientation.Vertical ? 1 : 10, - Height = orientation == Orientation.Vertical ? 10 : 1, - Orientation = orientation, - Size = size, SliderPosition = position, - KeepContentInAllViewport = true - }; - var top = new Toplevel (); - top.Add (scrollBar); - RunState rs = Application.Begin (top); - Application.RunIteration (ref rs); - - _ = TestHelpers.AssertDriverContentsWithFrameAre (output, _output); - - Assert.Null (Application.MouseGrabView); - - if (mouseFlags.HasFlag (MouseFlags.ReportMousePosition)) - { - MouseFlags mf = mouseFlags & ~MouseFlags.ReportMousePosition; - - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), - Flags = mf - }); - Application.RunIteration (ref rs); - - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (0, endLocation) : new (endLocation, 0), - Flags = mouseFlags - }); - Application.RunIteration (ref rs); - } - else - { - Assert.Equal (startLocation, endLocation); - - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), - Flags = mouseFlags - }); - Application.RunIteration (ref rs); - } - - Assert.Equal ("scrollSlider", Application.MouseGrabView?.Id); - Assert.IsType (Application.MouseGrabView); - Assert.Equal (expectedPos, scrollBar.SliderPosition); - - Application.RunIteration (ref rs); - _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); - - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), - Flags = MouseFlags.Button1Released - }); - Assert.Null (Application.MouseGrabView); - } - - [Theory] - [AutoInitShutdown] - [InlineData (Orientation.Vertical)] - [InlineData (Orientation.Horizontal)] - public void Mouse_Pressed_On_ScrollButton_Changes_Position_KeepContentInAllViewport_True (Orientation orientation) - { - var scrollBar = new ScrollBar - { - X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, - Orientation = orientation, KeepContentInAllViewport = true - }; - var top = new Toplevel (); - top.Add (scrollBar); - RunState rs = Application.Begin (top); - - var scroll = (Scroll)scrollBar.Subviews.FirstOrDefault (x => x is Scroll); - Rectangle scrollSliderFrame = scroll!.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame; - Assert.Equal (scrollSliderFrame, orientation == Orientation.Vertical ? new (0, 0, 1, 4) : new (0, 0, 4, 1)); - Assert.Equal (0, scrollBar.SliderPosition); - - Application.RunIteration (ref rs); - - // ScrollButton increase - for (var i = 0; i < 11; i++) - { - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (10, 19) : new (19, 10), Flags = MouseFlags.Button1Pressed - }); - Application.RunIteration (ref rs); - - if (i < 10) - { - Assert.Equal (i + 1, scrollBar.SliderPosition); - } - else - { - Assert.Equal (i, scrollBar.SliderPosition); - - Assert.Equal ( - orientation == Orientation.Vertical ? new (0, 4) : new (4, 0), - scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); - } - } - - for (var i = 10; i > -1; i--) - { - Application. - RaiseMouseEvent (new () { ScreenPosition = new (10, 10), Flags = MouseFlags.Button1Pressed }); - Application.RunIteration (ref rs); - - if (i > 0) - { - Assert.Equal (i - 1, scrollBar.SliderPosition); - } - else - { - Assert.Equal (0, scrollBar.SliderPosition); - Assert.Equal (new (0, 0), scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); - } - } - } - - [Theory] + [Theory (Skip = "Disabled - Will put this feature in View")] [AutoInitShutdown] [InlineData (Orientation.Vertical)] [InlineData (Orientation.Horizontal)] @@ -896,171 +233,6 @@ public void Moving_Mouse_Outside_Host_Ensures_Correct_Location_KeepContentInAllV scroll.Subviews.FirstOrDefault (x => x is ScrollSlider)!.Frame.Location); } - [Theory] - [InlineData (Orientation.Vertical, 20, 10, 10)] - [InlineData (Orientation.Vertical, 40, 30, 30)] - public void Position_Cannot_Be_Negative_Nor_Greater_Than_Size_Minus_Frame_Length_KeepContentInAllViewport_True (Orientation orientation, int size, int expectedPos1, int expectedPos2) - { - var scrollBar = new ScrollBar { Orientation = orientation, Height = 10, Size = size, KeepContentInAllViewport = true }; - Assert.Equal (0, scrollBar.SliderPosition); - - scrollBar.SliderPosition = -1; - scrollBar.Layout (); - Assert.Equal (0, scrollBar.SliderPosition); - - scrollBar.SliderPosition = size; - scrollBar.Layout (); - Assert.Equal (expectedPos1, scrollBar.SliderPosition); - - scrollBar.SliderPosition = expectedPos2; - scrollBar.Layout (); - Assert.Equal (expectedPos2, scrollBar.SliderPosition); - } - - [Fact] - public void PositionChanging_Cancelable_And_PositionChanged_Events () - { - var changingCount = 0; - var changedCount = 0; - var scrollBar = new ScrollBar { Size = 10 }; - - scrollBar.SliderPositionChanging += (s, e) => - { - if (changingCount == 0) - { - e.Cancel = true; - } - - changingCount++; - }; - scrollBar.SliderPositionChanged += (s, e) => changedCount++; - - scrollBar.SliderPosition = 1; - Assert.Equal (0, scrollBar.SliderPosition); - Assert.Equal (1, changingCount); - Assert.Equal (0, changedCount); - - scrollBar.SliderPosition = 1; - scrollBar.Layout (); - Assert.Equal (1, scrollBar.SliderPosition); - Assert.Equal (2, changingCount); - Assert.Equal (1, changedCount); - } - - [Fact] - public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position_Was_Really_Changed_KeepContentInAllViewport_True () - { - var changing = 0; - var cancel = false; - var changed = 0; - var scrollBar = new ScrollBar { Height = 10, Size = 20, KeepContentInAllViewport = true }; - scrollBar.SliderPositionChanging += Scroll_PositionChanging; - scrollBar.SliderPositionChanged += Scroll_PositionChanged; - - Assert.Equal (Orientation.Vertical, scrollBar.Orientation); - scrollBar.Layout (); - Assert.Equal (new (0, 0, 1, 10), scrollBar.Viewport); - Assert.Equal (0, scrollBar.SliderPosition); - Assert.Equal (0, changing); - Assert.Equal (0, changed); - - scrollBar.SliderPosition = 0; - scrollBar.Layout (); - Assert.Equal (0, scrollBar.SliderPosition); - Assert.Equal (0, changing); - Assert.Equal (0, changed); - - scrollBar.SliderPosition = 1; - scrollBar.Layout (); - Assert.Equal (1, scrollBar.SliderPosition); - Assert.Equal (1, changing); - Assert.Equal (1, changed); - - Reset (); - cancel = true; - scrollBar.SliderPosition = 2; - scrollBar.Layout (); - Assert.Equal (1, scrollBar.SliderPosition); - Assert.Equal (1, changing); - Assert.Equal (0, changed); - - Reset (); - scrollBar.SliderPosition = 10; - scrollBar.Layout (); - Assert.Equal (10, scrollBar.SliderPosition); - Assert.Equal (1, changing); - Assert.Equal (1, changed); - - Reset (); - scrollBar.SliderPosition = 11; - scrollBar.Layout (); - Assert.Equal (10, scrollBar.SliderPosition); - Assert.Equal (0, changing); - Assert.Equal (0, changed); - - Reset (); - scrollBar.SliderPosition = 12; - scrollBar.Layout (); - Assert.Equal (10, scrollBar.SliderPosition); - Assert.Equal (0, changing); - Assert.Equal (0, changed); - - Reset (); - scrollBar.SliderPosition = 13; - scrollBar.Layout (); - Assert.Equal (10, scrollBar.SliderPosition); - Assert.Equal (0, changing); - Assert.Equal (0, changed); - - Reset (); - scrollBar.SliderPosition = 0; - scrollBar.Layout (); - Assert.Equal (0, scrollBar.SliderPosition); - Assert.Equal (1, changing); - Assert.Equal (1, changed); - - scrollBar.SliderPositionChanging -= Scroll_PositionChanging; - scrollBar.SliderPositionChanged -= Scroll_PositionChanged; - - void Scroll_PositionChanging (object sender, CancelEventArgs e) - { - changing++; - e.Cancel = cancel; - } - - void Scroll_PositionChanged (object sender, EventArgs e) { changed++; } - - void Reset () - { - changing = 0; - cancel = false; - changed = 0; - } - } - - //[Fact] - //public void ShowScrollIndicator_CheckScrollBarVisibility () - //{ - // var scrollBar = new ScrollBar { Width = 2, Height = Dim.Fill (), Size = 30 }; - // View scrollBarSuperView = ScrollBarSuperView (); - // scrollBarSuperView.Add (scrollBar); - // Application.Begin ((scrollBarSuperView.SuperView as Toplevel)!); - - // Assert.True (scrollBar.ShowScrollIndicator); - // Assert.True (scrollBar.Visible); - - // scrollBar.ShowScrollIndicator = false; - // Assert.True (scrollBar.AutoHide); - // Assert.True (scrollBar.ShowScrollIndicator); - // Assert.True (scrollBar.Visible); - - // scrollBar.AutoHide = false; - // Assert.False (scrollBar.ShowScrollIndicator); - // Assert.False (scrollBar.Visible); - - // scrollBarSuperView.SuperView!.Dispose (); - //} - [Fact] public void Size_Cannot_Be_Negative () { @@ -1085,40 +257,42 @@ public void SizeChanged_Event () [Theory] [SetupFakeDriver] [InlineData ( - 3, 10, 1, - Orientation.Vertical, + 20, + 0, + Orientation.Horizontal, @" -┌─┐ -│▲│ -│█│ -│█│ -│░│ -│░│ -│░│ -│░│ -│▼│ -└─┘")] +┌──────────┐ +│◄███░░░░░►│ +└──────────┘")] + [InlineData ( 10, 3, + 20, 1, Orientation.Horizontal, @" -┌────────┐ -│◄██░░░░►│ -└────────┘")] +┌──────────┐ +│ ░███░░░░ │ +│◄░███░░░░►│ +│ ░███░░░░ │ +└──────────┘")] + [InlineData ( 3, 10, - 3, + 20, + 0, Orientation.Vertical, @" ┌───┐ │ ▲ │ │███│ │███│ +│███│ +│░░░│ │░░░│ │░░░│ │░░░│ @@ -1126,33 +300,54 @@ public void SizeChanged_Event () │ ▼ │ └───┘")] [InlineData ( + 6, 10, - 3, - 3, - Orientation.Horizontal, + 20, + 1, + Orientation.Vertical, @" -┌────────┐ -│ ██░░░░ │ -│◄██░░░░►│ -│ ██░░░░ │ -└────────┘")] - public void Vertical_Horizontal_Draws_Correctly (int sizeWidth, int sizeHeight, int widthHeight, Orientation orientation, string expected) +┌──────┐ +│ ▲ │ +│░░░░░░│ +│██████│ +│██████│ +│██████│ +│░░░░░░│ +│░░░░░░│ +│░░░░░░│ +│░░░░░░│ +│ ▼ │ +└──────┘")] + + + public void Draws_Correctly (int superViewportWidth, int superViewportHeight, int sliderSize, int sliderPosition, Orientation orientation, string expected) { var super = new Window { Id = "super", - Width = sizeWidth + (orientation == Orientation.Vertical ? widthHeight - 1 : 0), - Height = sizeHeight + (orientation == Orientation.Vertical ? 0 : widthHeight - 1) + Width = superViewportWidth + 2, + Height = superViewportHeight + 2 }; var scrollBar = new ScrollBar { Orientation = orientation, - Size = orientation == Orientation.Vertical ? sizeHeight * 2 : sizeWidth * 2, - Width = orientation == Orientation.Vertical ? widthHeight : Dim.Fill (), - Height = orientation == Orientation.Vertical ? Dim.Fill () : widthHeight }; + + if (orientation == Orientation.Vertical) + { + scrollBar.Width = Dim.Fill (); + } + else + { + scrollBar.Height = Dim.Fill (); + } super.Add (scrollBar); + + scrollBar.Size = sliderSize; + scrollBar.Layout (); + scrollBar.SliderPosition = sliderPosition; + super.BeginInit (); super.EndInit (); super.Layout (); @@ -1174,4 +369,86 @@ private View ScrollBarSuperView () return view; } + + + [Theory] + [CombinatorialData] + [AutoInitShutdown] + public void Mouse_Click_DecrementButton_Decrements ([CombinatorialRange (1, 3, 1)] int increment, Orientation orientation) + { + var top = new Toplevel () + { + Id = "top", + Width = 10, + Height = 10 + }; + var scrollBar = new ScrollBar + { + Id = "scrollBar", + Orientation = orientation, + Size = 20, + Increment = increment + }; + + top.Add (scrollBar); + RunState rs = Application.Begin (top); + scrollBar.SliderPosition = 5; + Application.RunIteration (ref rs); + + Assert.Equal (5, scrollBar.SliderPosition); + Assert.Equal (12, scrollBar.ContentPosition); + int initialPos = scrollBar.ContentPosition; + + Application.RaiseMouseEvent (new () + { + ScreenPosition = new (0, 0), + Flags = MouseFlags.Button1Clicked + }); + Application.RunIteration (ref rs); + + Assert.Equal (initialPos - increment, scrollBar.ContentPosition); + + Application.ResetState (true); + } + + + [Theory] + [CombinatorialData] + [AutoInitShutdown] + public void Mouse_Click_IncrementButton_Increments ([CombinatorialRange (1, 3, 1)] int increment, Orientation orientation) + { + var top = new Toplevel () + { + Id = "top", + Width = 10, + Height = 10 + }; + var scrollBar = new ScrollBar + { + Id = "scrollBar", + Orientation = orientation, + Size = 20, + Increment = increment + }; + + top.Add (scrollBar); + RunState rs = Application.Begin (top); + scrollBar.SliderPosition = 0; + Application.RunIteration (ref rs); + + Assert.Equal (0, scrollBar.SliderPosition); + Assert.Equal (0, scrollBar.ContentPosition); + int initialPos = scrollBar.ContentPosition; + + Application.RaiseMouseEvent (new () + { + ScreenPosition = orientation == Orientation.Vertical ? new (0, scrollBar.Frame.Height - 1) : new (scrollBar.Frame.Width - 1, 0), + Flags = MouseFlags.Button1Clicked + }); + Application.RunIteration (ref rs); + + Assert.Equal (initialPos + increment, scrollBar.ContentPosition); + + Application.ResetState (true); + } } diff --git a/UnitTests/Views/ScrollSliderTests.cs b/UnitTests/Views/ScrollSliderTests.cs index 066c7afdd6..b3673e5fcf 100644 --- a/UnitTests/Views/ScrollSliderTests.cs +++ b/UnitTests/Views/ScrollSliderTests.cs @@ -445,4 +445,26 @@ public void Draws_Correctly (int superViewportWidth, int superViewportHeight, in _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); } + + [Fact] + public void ShowPercent_True_ShowsPercentage () + { + View super = new () + { + Id = "super", + Width = 10, + Height = 10 + }; + ScrollSlider scrollSlider = new () + { + Id = "scrollSlider", + Height = 10, + Width = 10, + }; + super.Add (scrollSlider); + scrollSlider.ShowPercent = true; + Assert.True (scrollSlider.ShowPercent); + super.Draw (); + Assert.Contains ("0%", scrollSlider.Text); + } } diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index 3fc8e7a805..33a68fdc94 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -258,568 +258,142 @@ public void Constructor_Defaults () // top.Dispose (); //} - [Theory] - [AutoInitShutdown] - [InlineData ( - Orientation.Vertical, - 20, - 10, - 4, - @" -░ -░ -░ -░ -░ -█ -█ -█ -█ -█", - 0, - @" -█ -█ -█ -█ -█ -░ -░ -░ -░ -░")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 5, - @" -░ -░ -█ -█ -█ -░ -░ -░ -░ -░", - 20, - @" -░ -░ -░ -░ -░ -█ -█ -█ -░ -░")] - [InlineData ( - Orientation.Horizontal, - 20, - 10, - 4, - @" -░░░░░█████", - 0, - @" -█████░░░░░")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 5, - @" -░░███░░░░░", - 20, - @" -░░░░░███░░")] - public void Mouse_On_The_Container (Orientation orientation, int size, int position, int location, string output, int expectedPos, string expectedOut) - { - var scroll = new Scroll - { - Width = orientation == Orientation.Vertical ? 1 : 10, - Height = orientation == Orientation.Vertical ? 10 : 1, - Orientation = orientation, Size = size, - SliderPosition = position, - }; - var top = new Toplevel (); - top.Add (scroll); - RunState rs = Application.Begin (top); - Application.RunIteration (ref rs); - - _ = TestHelpers.AssertDriverContentsWithFrameAre (output, _output); - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (0, location) : new Point (location, 0), - Flags = MouseFlags.Button1Pressed - }); - Assert.Equal (expectedPos, scroll.SliderPosition); - - Application.RunIteration (ref rs); - _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); - } + //[Theory] + //[AutoInitShutdown] + //[InlineData (Orientation.Vertical)] + //[InlineData (Orientation.Horizontal)] + //public void Moving_Mouse_Outside_Host_Ensures_Correct_Location_KeepContentInAllViewport_True (Orientation orientation) + //{ + // var scroll = new Scroll + // { + // X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, + // SliderPosition = 5, Orientation = orientation + // }; + // var top = new Toplevel (); + // top.Add (scroll); + // RunState rs = Application.Begin (top); + + // Rectangle scrollSliderFrame = scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame; + // Assert.Equal (scrollSliderFrame, orientation == Orientation.Vertical ? new (0, 2, 1, 5) : new (2, 0, 5, 1)); + + // Application.RaiseMouseEvent (new () { ScreenPosition = orientation == Orientation.Vertical ? new (10, 12) : new (12, 10), Flags = MouseFlags.Button1Pressed }); + // Application.RunIteration (ref rs); + + // Application.RaiseMouseEvent ( + // new () + // { + // ScreenPosition = orientation == Orientation.Vertical ? new (10, 0) : new (0, 10), + // Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + // }); + // Application.RunIteration (ref rs); + // Assert.Equal (new (0, 0), scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame.Location); + + // Application.RaiseMouseEvent ( + // new () + // { + // ScreenPosition = orientation == Orientation.Vertical ? new (0, 25) : new (80, 0), + // Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + // }); + // Application.RunIteration (ref rs); + + // Assert.Equal ( + // orientation == Orientation.Vertical ? new (0, 5) : new (5, 0), + // scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame.Location); + //} [Theory] + [CombinatorialData] [AutoInitShutdown] - [InlineData ( - Orientation.Vertical, - 20, - 10, - 5, - 5, - @" -░ -░ -░ -░ -░ -█ -█ -█ -█ -█", - MouseFlags.Button1Pressed, - 10, - @" -░ -░ -░ -░ -░ -█ -█ -█ -█ -█")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 3, - 3, - @" -░ -░ -█ -█ -█ -░ -░ -░ -░ -░", - MouseFlags.Button1Pressed, - 10, - @" -░ -░ -█ -█ -█ -░ -░ -░ -░ -░")] - [InlineData ( - Orientation.Horizontal, - 20, - 10, - 5, - 5, - @" -░░░░░█████", - MouseFlags.Button1Pressed, - 10, - @" -░░░░░█████")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 3, - 3, - @" -░░███░░░░░", - MouseFlags.Button1Pressed, - 10, - @" -░░███░░░░░")] - [InlineData ( - Orientation.Vertical, - 20, - 10, - 5, - 4, - @" -░ -░ -░ -░ -░ -█ -█ -█ -█ -█", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 8, - @" -░ -░ -░ -░ -█ -█ -█ -█ -█ -░")] - [InlineData ( - Orientation.Horizontal, - 20, - 10, - 5, - 4, - @" -░░░░░█████", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 8, - @" -░░░░█████░")] - [InlineData ( - Orientation.Vertical, - 20, - 10, - 5, - 6, - @" -░ -░ -░ -░ -░ -█ -█ -█ -█ -█", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 10, - @" -░ -░ -░ -░ -░ -█ -█ -█ -█ -█")] - [InlineData ( - Orientation.Horizontal, - 20, - 10, - 5, - 6, - @" -░░░░░█████", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 10, - @" -░░░░░█████")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 2, - 1, - @" -░ -░ -█ -█ -█ -░ -░ -░ -░ -░", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 4, - @" -░ -█ -█ -█ -░ -░ -░ -░ -░ -░")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 2, - 1, - @" -░░███░░░░░", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 4, - @" -░███░░░░░░")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 3, - 4, - @" -░ -░ -█ -█ -█ -░ -░ -░ -░ -░", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 12, - @" -░ -░ -░ -█ -█ -█ -░ -░ -░ -░")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 3, - 4, - @" -░░███░░░░░", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 12, - @" -░░░███░░░░")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 2, - 3, - @" -░ -░ -█ -█ -█ -░ -░ -░ -░ -░", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 12, - @" -░ -░ -░ -█ -█ -█ -░ -░ -░ -░")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 2, - 3, - @" -░░███░░░░░", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 12, - @" -░░░███░░░░")] - [InlineData ( - Orientation.Vertical, - 40, - 10, - 2, - 4, - @" -░ -░ -█ -█ -█ -░ -░ -░ -░ -░", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 16, - @" -░ -░ -░ -░ -█ -█ -█ -░ -░ -░")] - [InlineData ( - Orientation.Horizontal, - 40, - 10, - 2, - 4, - @" -░░███░░░░░", - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition, - 16, - @" -░░░░███░░░")] - public void Mouse_On_The_Slider ( - Orientation orientation, - int size, - int position, - int startLocation, - int endLocation, - string output, - MouseFlags mouseFlags, - int expectedPos, - string expectedOut - ) + public void Mouse_Wheel_Increments_Position ([CombinatorialRange (1, 3, 1)] int increment, Orientation orientation) { + var top = new Toplevel () + { + Id = "top", + Width = 10, + Height = 10 + }; var scroll = new Scroll { - Width = orientation == Orientation.Vertical ? 1 : 10, - Height = orientation == Orientation.Vertical ? 10 : 1, + Id = "scroll", Orientation = orientation, - Size = size, SliderPosition = position, + Size = 20, + Increment = increment }; - var top = new Toplevel (); + top.Add (scroll); RunState rs = Application.Begin (top); - Application.RunIteration (ref rs); + Assert.Equal (0, scroll.SliderPosition); + Assert.Equal (0, scroll.ContentPosition); - _ = TestHelpers.AssertDriverContentsWithFrameAre (output, _output); + Application.RaiseMouseEvent (new () + { + ScreenPosition = new (0, 0), + Flags = orientation == Orientation.Vertical ? MouseFlags.WheeledDown : MouseFlags.WheeledRight + }); + Application.RunIteration (ref rs); - Assert.Null (Application.MouseGrabView); + Assert.Equal (increment, scroll.ContentPosition); - if (mouseFlags.HasFlag (MouseFlags.ReportMousePosition)) - { - MouseFlags mf = mouseFlags & ~MouseFlags.ReportMousePosition; - - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), - Flags = mf - }); - Application.RunIteration (ref rs); - - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (0, endLocation) : new (endLocation, 0), - Flags = mouseFlags - }); - Application.RunIteration (ref rs); - } - else + Application.RaiseMouseEvent (new () { - Assert.Equal (startLocation, endLocation); - - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), - Flags = mouseFlags - }); - Application.RunIteration (ref rs); - } + ScreenPosition = new (0, 0), + Flags = orientation == Orientation.Vertical ? MouseFlags.WheeledDown : MouseFlags.WheeledRight + }); + Application.RunIteration (ref rs); - Assert.Equal ("scrollSlider", Application.MouseGrabView?.Id); - Assert.Equal (expectedPos, scroll.SliderPosition); + Assert.Equal (increment * 2, scroll.ContentPosition); + Application.RaiseMouseEvent (new () + { + ScreenPosition = new (0, 0), + Flags = orientation == Orientation.Vertical ? MouseFlags.WheeledUp : MouseFlags.WheeledLeft + }); Application.RunIteration (ref rs); - _ = TestHelpers.AssertDriverContentsWithFrameAre (expectedOut, _output); - - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (0, startLocation) : new (startLocation, 0), - Flags = MouseFlags.Button1Released - }); - Assert.Null (Application.MouseGrabView); + + Assert.Equal (increment, scroll.ContentPosition); + + Application.ResetState (true); } [Theory] + [CombinatorialData] [AutoInitShutdown] - [InlineData (Orientation.Vertical)] - [InlineData (Orientation.Horizontal)] - public void Moving_Mouse_Outside_Host_Ensures_Correct_Location_KeepContentInAllViewport_True (Orientation orientation) + public void Mouse_Click_Outside_Slider_Moves (Orientation orientation) { + var top = new Toplevel () + { + Id = "top", + Width = 10, + Height = 10 + }; var scroll = new Scroll { - X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20, - SliderPosition = 5, Orientation = orientation + Id = "scroll", + Orientation = orientation, + Size = 20, }; - var top = new Toplevel (); + top.Add (scroll); RunState rs = Application.Begin (top); + scroll.SliderPosition = 5; + Application.RunIteration (ref rs); - Rectangle scrollSliderFrame = scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame; - Assert.Equal (scrollSliderFrame, orientation == Orientation.Vertical ? new (0, 2, 1, 5) : new (2, 0, 5, 1)); + Assert.Equal (5, scroll.SliderPosition); + Assert.Equal (10, scroll.ContentPosition); - Application.RaiseMouseEvent (new () { ScreenPosition = orientation == Orientation.Vertical ? new (10, 12) : new (12, 10), Flags = MouseFlags.Button1Pressed }); + Application.RaiseMouseEvent (new () + { + ScreenPosition = new (0, 0), + Flags = MouseFlags.Button1Clicked + }); Application.RunIteration (ref rs); - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (10, 0) : new (0, 10), - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); - Application.RunIteration (ref rs); - Assert.Equal (new (0, 0), scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame.Location); - - Application.RaiseMouseEvent ( - new () - { - ScreenPosition = orientation == Orientation.Vertical ? new (0, 25) : new (80, 0), - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); - Application.RunIteration (ref rs); + Assert.Equal (0, scroll.SliderPosition); + Assert.Equal (0, scroll.ContentPosition); - Assert.Equal ( - orientation == Orientation.Vertical ? new (0, 5) : new (5, 0), - scroll.Subviews.FirstOrDefault (x => x.Id == "scrollSlider")!.Frame.Location); + Application.ResetState (true); } [Theory] @@ -840,7 +414,7 @@ Orientation orientation var scroll = new Scroll { Orientation = orientation, - Width = Dim.Fill(), + Width = Dim.Fill (), Height = Dim.Fill () }; super.Add (scroll); @@ -854,11 +428,13 @@ Orientation orientation } [Fact] - public void PositionChanging_Cancelable_And_PositionChanged_Events () + public void SliderPosition_Event_Cancelables () { var changingCount = 0; var changedCount = 0; - var scroll = new Scroll { Size = 10 }; + var scroll = new Scroll { }; + scroll.Layout (); + scroll.Size = scroll.Viewport.Height * 2; scroll.Layout (); scroll.SliderPositionChanging += (s, e) => @@ -919,13 +495,13 @@ public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position Reset (); scroll.SliderPosition = 10; - Assert.Equal (10, scroll.SliderPosition); + Assert.Equal (5, scroll.SliderPosition); Assert.Equal (1, changing); Assert.Equal (1, changed); Reset (); scroll.SliderPosition = 11; - Assert.Equal (10, scroll.SliderPosition); + Assert.Equal (5, scroll.SliderPosition); Assert.Equal (0, changing); Assert.Equal (0, changed); @@ -979,196 +555,33 @@ public void SizeChanged_Event () [Theory] [SetupFakeDriver] [InlineData ( - 3, 10, 1, - 0, - Orientation.Vertical, - @" -┌───┐ -│███│ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└───┘")] - [InlineData ( - 10, - 1, - 3, + 20, 0, Orientation.Horizontal, @" ┌──────────┐ -│███ │ +│█████░░░░░│ └──────────┘")] - [InlineData ( - 3, - 10, - 3, - 0, - Orientation.Vertical, - @" -┌───┐ -│███│ -│███│ -│███│ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└───┘")] - - [InlineData ( - 3, 10, - 5, - 0, - Orientation.Vertical, - @" -┌───┐ -│███│ -│███│ -│███│ -│███│ -│███│ -│ │ -│ │ -│ │ -│ │ -│ │ -└───┘")] - - [InlineData ( 3, - 10, - 5, + 20, 1, - Orientation.Vertical, - @" -┌───┐ -│ │ -│███│ -│███│ -│███│ -│███│ -│███│ -│ │ -│ │ -│ │ -│ │ -└───┘")] - [InlineData ( - 3, - 10, - 5, - 4, - Orientation.Vertical, - @" -┌───┐ -│ │ -│ │ -│ │ -│ │ -│███│ -│███│ -│███│ -│███│ -│███│ -│ │ -└───┘")] - [InlineData ( - 3, - 10, - 5, - 5, - Orientation.Vertical, - @" -┌───┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│███│ -│███│ -│███│ -│███│ -│███│ -└───┘")] - [InlineData ( - 3, - 10, - 5, - 6, - Orientation.Vertical, - @" -┌───┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│███│ -│███│ -│███│ -│███│ -│███│ -└───┘")] - - [InlineData ( - 3, - 10, - 10, - 0, - Orientation.Vertical, + Orientation.Horizontal, @" -┌───┐ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -└───┘")] +┌──────────┐ +│░█████░░░░│ +│░█████░░░░│ +│░█████░░░░│ +└──────────┘")] [InlineData ( 3, 10, - 10, - 5, - Orientation.Vertical, - @" -┌───┐ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -└───┘")] - [InlineData ( - 3, - 10, - 11, + 20, 0, Orientation.Vertical, @" @@ -1178,112 +591,14 @@ public void SizeChanged_Event () │███│ │███│ │███│ -│███│ -│███│ -│███│ -│███│ -│███│ +│░░░│ +│░░░│ +│░░░│ +│░░░│ +│░░░│ └───┘")] - [InlineData ( - 10, - 3, - 5, - 0, - Orientation.Horizontal, - @" -┌──────────┐ -│█████ │ -│█████ │ -│█████ │ -└──────────┘")] - [InlineData ( - 10, - 3, - 5, - 1, - Orientation.Horizontal, - @" -┌──────────┐ -│ █████ │ -│ █████ │ -│ █████ │ -└──────────┘")] - [InlineData ( - 10, - 3, - 5, - 4, - Orientation.Horizontal, - @" -┌──────────┐ -│ █████ │ -│ █████ │ -│ █████ │ -└──────────┘")] - [InlineData ( - 10, - 3, - 5, - 5, - Orientation.Horizontal, - @" -┌──────────┐ -│ █████│ -│ █████│ -│ █████│ -└──────────┘")] - [InlineData ( - 10, - 3, - 5, - 6, - Orientation.Horizontal, - @" -┌──────────┐ -│ █████│ -│ █████│ -│ █████│ -└──────────┘")] - - [InlineData ( - 10, - 3, - 10, - 0, - Orientation.Horizontal, - @" -┌──────────┐ -│██████████│ -│██████████│ -│██████████│ -└──────────┘")] - - [InlineData ( - 10, - 3, - 10, - 5, - Orientation.Horizontal, - @" -┌──────────┐ -│██████████│ -│██████████│ -│██████████│ -└──────────┘")] - [InlineData ( - 10, - 3, - 11, - 0, - Orientation.Horizontal, - @" -┌──────────┐ -│██████████│ -│██████████│ -│██████████│ -└──────────┘")] public void Draws_Correctly (int superViewportWidth, int superViewportHeight, int sliderSize, int sliderPosition, Orientation orientation, string expected) { var super = new Window @@ -1297,6 +612,15 @@ public void Draws_Correctly (int superViewportWidth, int superViewportHeight, in { Orientation = orientation, }; + + if (orientation == Orientation.Vertical) + { + scroll.Width = Dim.Fill (); + } + else + { + scroll.Height = Dim.Fill (); + } super.Add (scroll); scroll.Size = sliderSize; From 805f7023e5eb90adfce1bc45870de31356bec398 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 9 Nov 2024 23:54:10 -0700 Subject: [PATCH 083/128] Scrolling scenario upgraded --- Terminal.Gui/View/View.ScrollBars.cs | 34 +++-- Terminal.Gui/Views/Scroll/Scroll.cs | 2 +- UICatalog/Scenarios/ContentScrolling.cs | 2 +- UICatalog/Scenarios/Scrolling.cs | 182 +++++++++--------------- 4 files changed, 90 insertions(+), 130 deletions(-) diff --git a/Terminal.Gui/View/View.ScrollBars.cs b/Terminal.Gui/View/View.ScrollBars.cs index cbea93ee45..d3e56344fd 100644 --- a/Terminal.Gui/View/View.ScrollBars.cs +++ b/Terminal.Gui/View/View.ScrollBars.cs @@ -43,10 +43,15 @@ private void SetupScrollBars () Bottom = scrollBar.Visible ? Padding.Thickness.Bottom + 1 : 0 }; - scrollBar.SliderPositionChanged += (_, args) => - { - Viewport = Viewport with { X = args.CurrentValue }; - }; + scrollBar.ContentPositionChanged += (_, args) => + { + Viewport = Viewport with + { + X = Math.Min ( + args.CurrentValue, + GetContentSize ().Width - (Viewport.Width)) + }; + }; scrollBar.VisibleChanged += (_, _) => { @@ -96,18 +101,23 @@ private void SetupScrollBars () Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : 0 }; - scrollBar.SliderPositionChanged += (_, args) => - { - Viewport = Viewport with { Y = args.CurrentValue }; - }; + scrollBar.ContentPositionChanged += (_, args) => + { + Viewport = Viewport with + { + Y = Math.Min ( + args.CurrentValue, + GetContentSize ().Height - (Viewport.Height)) + }; + }; scrollBar.VisibleChanged += (_, _) => { Padding.Thickness = Padding.Thickness with { Right = scrollBar.Visible - ? Padding.Thickness.Right + 1 - : Padding.Thickness.Right - 1 + ? Padding.Thickness.Right + 1 + : Padding.Thickness.Right - 1 }; }; } @@ -120,12 +130,12 @@ private void SetupScrollBars () { if (_verticalScrollBar.IsValueCreated) { - _verticalScrollBar.Value.SliderPosition = Viewport.Y; + _verticalScrollBar.Value.ContentPosition = Viewport.Y; } if (_horizontalScrollBar.IsValueCreated) { - _horizontalScrollBar.Value.SliderPosition = Viewport.X; + _horizontalScrollBar.Value.ContentPosition = Viewport.X; } }; diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs index e240d5cdb2..02ef593561 100644 --- a/Terminal.Gui/Views/Scroll/Scroll.cs +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -159,7 +159,7 @@ private void RaiseSliderPositionChangeEvents (int newSliderPosition) { int currentSliderPosition = CalculateSliderPosition (_contentPosition); - if (newSliderPosition > Size - ViewportDimension || currentSliderPosition == newSliderPosition) + if (/*newSliderPosition > Size - ViewportDimension ||*/ currentSliderPosition == newSliderPosition) { return; } diff --git a/UICatalog/Scenarios/ContentScrolling.cs b/UICatalog/Scenarios/ContentScrolling.cs index 1c57755411..dd65f84a74 100644 --- a/UICatalog/Scenarios/ContentScrolling.cs +++ b/UICatalog/Scenarios/ContentScrolling.cs @@ -125,7 +125,7 @@ public override void Main () app.Add (view); // Add Scroll Setting UI to Padding - view.Padding.Thickness = view.Padding.Thickness with { Top = view.Padding.Thickness.Top + 4 }; + view.Padding.Thickness = view.Padding.Thickness with { Top = view.Padding.Thickness.Top + 6 }; view.Padding.CanFocus = true; Label frameLabel = new () diff --git a/UICatalog/Scenarios/Scrolling.cs b/UICatalog/Scenarios/Scrolling.cs index 8d7cdac009..a87ea4faee 100644 --- a/UICatalog/Scenarios/Scrolling.cs +++ b/UICatalog/Scenarios/Scrolling.cs @@ -3,35 +3,25 @@ namespace UICatalog.Scenarios; -[ScenarioMetadata ("Scrolling", "Demonstrates scrolling etc...")] +[ScenarioMetadata ("Scrolling", "Content scrolling, IScrollBars, etc...")] [ScenarioCategory ("Controls")] [ScenarioCategory ("Scrolling")] [ScenarioCategory ("Tests")] public class Scrolling : Scenario { - private ViewDiagnosticFlags _diagnosticFlags; - public override void Main () { Application.Init (); - _diagnosticFlags = View.Diagnostics; - View.Diagnostics = ViewDiagnosticFlags.Ruler; var app = new Window { Title = GetQuitKeyAndName (), - - // Offset to stress clipping - X = 3, - Y = 3, - Width = Dim.Fill (3), - Height = Dim.Fill (3) }; var label = new Label { X = 0, Y = 0 }; app.Add (label); - var scrollView = new ScrollView + var scrollView = new IScrollView { Id = "scrollView", X = 2, @@ -39,53 +29,48 @@ public override void Main () Width = 60, Height = 20, ColorScheme = Colors.ColorSchemes ["TopLevel"], - - //ContentOffset = Point.Empty, - ShowVerticalScrollIndicator = true, - ShowHorizontalScrollIndicator = true + CanFocus = true, + BorderStyle = LineStyle.Heavy, + Arrangement = ViewArrangement.Resizable }; - // BUGBUG: set_ContentSize is supposed to be `protected`. - scrollView.SetContentSize (new (120, 40)); - scrollView.Padding.Thickness = new (1); - - label.Text = $"{scrollView}\nContentSize: {scrollView.GetContentSize ()}\nContentOffset: {scrollView.ContentOffset}"; + scrollView.SetContentSize (new (80, 25)); + //scrollView.Padding.Thickness = new (1); + //scrollView.Padding.Diagnostics = ViewDiagnosticFlags.Ruler; - const string rule = "0123456789"; - - var horizontalRuler = new Label + View rulerView = new View () { - X = 0, - Y = 0, - + Height = Dim.Fill (), Width = Dim.Fill (), - Height = 2, - ColorScheme = Colors.ColorSchemes ["Error"] }; - scrollView.Add (horizontalRuler); - - const string vrule = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n"; - - var verticalRuler = new Label + rulerView.Border.Thickness = new (1); + rulerView.Border.LineStyle = LineStyle.None; + rulerView.Border.Diagnostics = ViewDiagnosticFlags.Ruler; + rulerView.Border.ColorScheme = Colors.ColorSchemes ["Error"]; + + scrollView.Add (rulerView); + label.Text = + $"{scrollView}\nContentSize: {scrollView.GetContentSize ()}\nViewport.Location: {scrollView.Viewport.Location}"; + + scrollView.ViewportChanged += (_, _) => + { + label.Text = + $"{scrollView}\nContentSize: {scrollView.GetContentSize ()}\nViewport.Location: {scrollView.Viewport.Location}"; + }; + + var pressMeButton = new Button { - X = 0, - Y = 0, - - Width = 1, - Height = Dim.Fill (), - ColorScheme = Colors.ColorSchemes ["Error"] + X = 1, + Y = 1, + Text = "Press me!" }; - scrollView.Add (verticalRuler); - - var pressMeButton = new Button { X = 3, Y = 3, Text = "Press me!" }; pressMeButton.Accepting += (s, e) => MessageBox.Query (20, 7, "MessageBox", "Neat?", "Yes", "No"); scrollView.Add (pressMeButton); var aLongButton = new Button { - X = 3, - Y = 4, + X = Pos.Right (pressMeButton), + Y = Pos.Bottom (pressMeButton), - Width = Dim.Fill (3), Text = "A very long button. Should be wide enough to demo clipping!" }; aLongButton.Accepting += (s, e) => MessageBox.Query (20, 7, "MessageBox", "Neat?", "Yes", "No"); @@ -94,8 +79,8 @@ public override void Main () scrollView.Add ( new TextField { - X = 3, - Y = 5, + X = Pos.Left (pressMeButton), + Y = Pos.Bottom (aLongButton) + 1, Width = 50, ColorScheme = Colors.ColorSchemes ["Dialog"], Text = "This is a test of..." @@ -105,8 +90,8 @@ public override void Main () scrollView.Add ( new TextField { - X = 3, - Y = 10, + X = Pos.Left (pressMeButton), + Y = Pos.Bottom (aLongButton) + 3, Width = 50, ColorScheme = Colors.ColorSchemes ["Dialog"], Text = "... the emergency broadcast system." @@ -116,19 +101,21 @@ public override void Main () scrollView.Add ( new TextField { - X = 3, - Y = 99, + X = Pos.Left (pressMeButton), + Y = 40, Width = 50, - ColorScheme = Colors.ColorSchemes ["Dialog"], + ColorScheme = Colors.ColorSchemes ["Error"], Text = "Last line" } ); // Demonstrate AnchorEnd - Button is anchored to bottom/right - var anchorButton = new Button { Y = Pos.AnchorEnd (0) - 1, Text = "Bottom Right" }; - - // TODO: Use Pos.Width instead of (Right-Left) when implemented (#502) - anchorButton.X = Pos.AnchorEnd (0) - (Pos.Right (anchorButton) - Pos.Left (anchorButton)); + var anchorButton = new Button + { + X = Pos.AnchorEnd (), + Y = Pos.AnchorEnd (), + Text = "Bottom Right" + }; anchorButton.Accepting += (s, e) => { @@ -141,74 +128,51 @@ public override void Main () app.Add (scrollView); + scrollView.HorizontalScrollBar.Visible = true; var hCheckBox = new CheckBox { X = Pos.X (scrollView), Y = Pos.Bottom (scrollView), Text = "Horizontal Scrollbar", - CheckedState = scrollView.ShowHorizontalScrollIndicator ? CheckState.Checked : CheckState.UnChecked + CheckedState = scrollView.HorizontalScrollBar.Visible ? CheckState.Checked : CheckState.UnChecked }; app.Add (hCheckBox); + hCheckBox.CheckedStateChanged += (sender, args) => + { + scrollView.HorizontalScrollBar.Visible = args.CurrentValue == CheckState.Checked; + }; + scrollView.VerticalScrollBar.Visible = true; var vCheckBox = new CheckBox { X = Pos.Right (hCheckBox) + 3, Y = Pos.Bottom (scrollView), Text = "Vertical Scrollbar", - CheckedState = scrollView.ShowVerticalScrollIndicator ? CheckState.Checked : CheckState.UnChecked + CheckedState = scrollView.VerticalScrollBar.Visible ? CheckState.Checked : CheckState.UnChecked }; app.Add (vCheckBox); + vCheckBox.CheckedStateChanged += (sender, args) => + { + scrollView.VerticalScrollBar.Visible = args.CurrentValue == CheckState.Checked; + }; var t = "Auto Hide Scrollbars"; var ahCheckBox = new CheckBox { - X = Pos.Left (scrollView), Y = Pos.Bottom (hCheckBox), Text = t, CheckedState = scrollView.AutoHideScrollBars ? CheckState.Checked : CheckState.UnChecked - }; - var k = "Keep Content Always In Viewport"; - - var keepCheckBox = new CheckBox - { - X = Pos.Left (scrollView), Y = Pos.Bottom (ahCheckBox), Text = k, CheckedState = scrollView.AutoHideScrollBars ? CheckState.Checked : CheckState.UnChecked + X = Pos.Left (scrollView), Y = Pos.Bottom (hCheckBox), Text = t, + CheckedState = scrollView.HorizontalScrollBar.AutoHide ? CheckState.Checked : CheckState.UnChecked }; - hCheckBox.CheckedStateChanging += (s, e) => - { - if (ahCheckBox.CheckedState == CheckState.UnChecked) - { - scrollView.ShowHorizontalScrollIndicator = e.NewValue == CheckState.Checked; - } - else - { - hCheckBox.CheckedState = CheckState.Checked; - MessageBox.Query ("Message", "Disable Auto Hide Scrollbars first.", "Ok"); - } - }; - - vCheckBox.CheckedStateChanging += (s, e) => - { - if (ahCheckBox.CheckedState == CheckState.UnChecked) - { - scrollView.ShowVerticalScrollIndicator = e.NewValue == CheckState.Checked; - } - else - { - vCheckBox.CheckedState = CheckState.Checked; - MessageBox.Query ("Message", "Disable Auto Hide Scrollbars first.", "Ok"); - } - }; - ahCheckBox.CheckedStateChanging += (s, e) => { - scrollView.AutoHideScrollBars = e.NewValue == CheckState.Checked; + scrollView.HorizontalScrollBar.AutoHide = e.NewValue == CheckState.Checked; + scrollView.VerticalScrollBar.AutoHide = e.NewValue == CheckState.Checked; hCheckBox.CheckedState = CheckState.Checked; vCheckBox.CheckedState = CheckState.Checked; }; app.Add (ahCheckBox); - keepCheckBox.CheckedStateChanging += (s, e) => scrollView.KeepContentAlwaysInViewport = e.NewValue == CheckState.Checked; - app.Add (keepCheckBox); - var count = 0; var mousePos = new Label @@ -238,38 +202,24 @@ bool timer () Application.AddTimeout (TimeSpan.FromMilliseconds (300), timer); - app.Loaded += App_Loaded; app.Unloaded += app_Unloaded; + Application.Run (app); - app.Loaded -= App_Loaded; app.Unloaded -= app_Unloaded; app.Dispose (); Application.Shutdown (); return; - // Local functions - void App_Loaded (object sender, EventArgs args) - { - horizontalRuler.Text = - rule.Repeat ((int)Math.Ceiling (horizontalRuler.Viewport.Width / (double)rule.Length)) [ - ..horizontalRuler.Viewport.Width] - + "\n" - + "| ".Repeat ( - (int)Math.Ceiling (horizontalRuler.Viewport.Width / (double)rule.Length) - ) [ - ..horizontalRuler.Viewport.Width]; - - verticalRuler.Text = - vrule.Repeat ((int)Math.Ceiling (verticalRuler.Viewport.Height * 2 / (double)rule.Length)) - [..(verticalRuler.Viewport.Height * 2)]; - } - void app_Unloaded (object sender, EventArgs args) { - View.Diagnostics = _diagnosticFlags; pulsing = false; } } } + +public class IScrollView : View +{ + +} From 315b3cddaa0c02a8d10ab56554fb285621b22c60 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 10 Nov 2024 00:14:40 -0700 Subject: [PATCH 084/128] unit tests pass --- UnitTests/Views/ScrollTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UnitTests/Views/ScrollTests.cs b/UnitTests/Views/ScrollTests.cs index 33a68fdc94..7afb6afc8f 100644 --- a/UnitTests/Views/ScrollTests.cs +++ b/UnitTests/Views/ScrollTests.cs @@ -502,8 +502,8 @@ public void PositionChanging_PositionChanged_Events_Only_Raises_Once_If_Position Reset (); scroll.SliderPosition = 11; Assert.Equal (5, scroll.SliderPosition); - Assert.Equal (0, changing); - Assert.Equal (0, changed); + Assert.Equal (1, changing); + Assert.Equal (1, changed); Reset (); scroll.SliderPosition = 0; From 362c1d97af2a07739b30e6e48507f466f77815b6 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 10 Nov 2024 00:25:58 -0700 Subject: [PATCH 085/128] Removed legacy scrollview stuff. --- Terminal.Gui/Input/Responder.cs | 24 - Terminal.Gui/Views/ScrollBarView.cs | 1086 ------------- Terminal.Gui/Views/ScrollView.cs | 774 --------- UICatalog/Scenarios/ASCIICustomButton.cs | 216 +-- UICatalog/Scenarios/Clipping.cs | 20 +- UICatalog/Scenarios/CsvEditor.cs | 70 +- UICatalog/Scenarios/Editor.cs | 65 +- UICatalog/Scenarios/ListColumns.cs | 72 +- UICatalog/Scenarios/ListViewWithSelection.cs | 58 +- UICatalog/Scenarios/ListsAndCombos.cs | 108 +- UICatalog/Scenarios/TableEditor.cs | 70 +- UICatalog/Scenarios/TreeViewFileSystem.cs | 82 +- UICatalog/Scenarios/Wizards.cs | 52 +- .../Mouse/ApplicationMouseTests.cs | 82 +- UnitTests/Input/ResponderTests.cs | 90 -- UnitTests/Views/ScrollBarViewTests.cs | 1390 ----------------- UnitTests/Views/ScrollViewTests.cs | 1155 -------------- UnitTests/Views/ToplevelTests.cs | 55 - 18 files changed, 447 insertions(+), 5022 deletions(-) delete mode 100644 Terminal.Gui/Views/ScrollBarView.cs delete mode 100644 Terminal.Gui/Views/ScrollView.cs delete mode 100644 UnitTests/Views/ScrollBarViewTests.cs delete mode 100644 UnitTests/Views/ScrollViewTests.cs diff --git a/Terminal.Gui/Input/Responder.cs b/Terminal.Gui/Input/Responder.cs index 43cc082403..a7f01a4bb4 100644 --- a/Terminal.Gui/Input/Responder.cs +++ b/Terminal.Gui/Input/Responder.cs @@ -49,30 +49,6 @@ protected virtual void Dispose (bool disposing) } } - // TODO: v2 - nuke this - /// Utilty function to determine is overridden in the . - /// The view. - /// The method name. - /// if it's overridden, otherwise. - internal static bool IsOverridden (Responder subclass, string method) - { - MethodInfo m = subclass.GetType () - .GetMethod ( - method, - BindingFlags.Instance - | BindingFlags.Public - | BindingFlags.NonPublic - | BindingFlags.DeclaredOnly - ); - - if (m is null) - { - return false; - } - - return m.GetBaseDefinition ().DeclaringType != m.DeclaringType; - } - #if DEBUG_IDISPOSABLE /// For debug purposes to verify objects are being disposed properly public bool WasDisposed; diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs deleted file mode 100644 index 8873e5ba78..0000000000 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ /dev/null @@ -1,1086 +0,0 @@ -// -// ScrollBarView.cs: ScrollBarView view. -// -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// - -using System.Diagnostics; - -namespace Terminal.Gui; - -/// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical -/// -/// -/// The scrollbar is drawn to be a representation of the Size, assuming that the scroll position is set at -/// Position. -/// -/// If the region to display the scrollbar is larger than three characters, arrow indicators are drawn. -/// -public class ScrollBarView : View -{ - private bool _autoHideScrollBars = true; - private View _contentBottomRightCorner; - private bool _hosted; - private bool _keepContentAlwaysInViewport = true; - private int _lastLocation = -1; - private ScrollBarView _otherScrollBarView; - private int _posBarOffset; - private int _posBottomTee; - private int _posLeftTee; - private int _posRightTee; - private int _posTopTee; - private bool _showScrollIndicator; - private int _size, _position; - private bool _vertical; - - /// - /// Initializes a new instance of the class. - /// - public ScrollBarView () - { - WantContinuousButtonPressed = true; - - Added += (s, e) => CreateBottomRightCorner (e.SuperView); - Initialized += ScrollBarView_Initialized; - } - - /// - /// Initializes a new instance of the class. - /// - /// The view that will host this scrollbar. - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. - /// - /// If set to true (default) will have the other scrollbar, otherwise will - /// have only one. - /// - public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true) - { - if (host is null) - { - throw new ArgumentNullException ("The host parameter can't be null."); - } - - if (host.SuperView is null) - { - throw new ArgumentNullException ("The host SuperView parameter can't be null."); - } - - _hosted = true; - IsVertical = isVertical; - ColorScheme = host.ColorScheme; - X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host); - Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; - Host = host; - CanFocus = false; - Enabled = host.Enabled; - Visible = host.Visible; - Initialized += ScrollBarView_Initialized; - - //Host.CanFocusChanged += Host_CanFocusChanged; - Host.EnabledChanged += Host_EnabledChanged; - Host.VisibleChanged += Host_VisibleChanged; - Host.SuperView.Add (this); - AutoHideScrollBars = true; - - if (showBothScrollIndicator) - { - OtherScrollBarView = new ScrollBarView - { - IsVertical = !isVertical, - ColorScheme = host.ColorScheme, - Host = host, - CanFocus = false, - Enabled = host.Enabled, - Visible = host.Visible, - OtherScrollBarView = this - }; - OtherScrollBarView._hosted = true; - OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host); - OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; - OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView); - OtherScrollBarView.ShowScrollIndicator = true; - } - - ShowScrollIndicator = true; - CreateBottomRightCorner (Host); - } - - /// If true the vertical/horizontal scroll bars won't be showed if it's not needed. - public bool AutoHideScrollBars - { - get => _autoHideScrollBars; - set - { - if (_autoHideScrollBars != value) - { - _autoHideScrollBars = value; - SetNeedsDraw (); - } - } - } - - // BUGBUG: v2 - for consistency this should be named "Parent" not "Host" - /// Get or sets the view that host this - public View Host { get; internal set; } - - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. - public bool IsVertical - { - get => _vertical; - set - { - _vertical = value; - - if (IsInitialized) - { - SetWidthHeight (); - } - } - } - - /// Get or sets if the view-port is kept always visible in the area of this - public bool KeepContentAlwaysInViewport - { - get => _keepContentAlwaysInViewport; - set - { - if (_keepContentAlwaysInViewport != value) - { - _keepContentAlwaysInViewport = value; - var pos = 0; - - if (value && !_vertical && _position + Host.Viewport.Width > _size) - { - pos = _size - Host.Viewport.Width + (_showBothScrollIndicator ? 1 : 0); - } - - if (value && _vertical && _position + Host.Viewport.Height > _size) - { - pos = _size - Host.Viewport.Height + (_showBothScrollIndicator ? 1 : 0); - } - - if (pos != 0) - { - Position = pos; - } - - if (OtherScrollBarView is { } && OtherScrollBarView._keepContentAlwaysInViewport != value) - { - OtherScrollBarView.KeepContentAlwaysInViewport = value; - } - - if (pos == 0) - { - Refresh (); - } - } - } - } - - /// Represent a vertical or horizontal ScrollBarView other than this. - public ScrollBarView OtherScrollBarView - { - get => _otherScrollBarView; - set - { - if (value is { } && ((value.IsVertical && _vertical) || (!value.IsVertical && !_vertical))) - { - throw new ArgumentException ( - $"There is already a {(_vertical ? "vertical" : "horizontal")} ScrollBarView." - ); - } - - _otherScrollBarView = value; - } - } - - /// The position, relative to , to set the scrollbar at. - /// The position. - public int Position - { - get => _position; - set - { - if (_position == value) - { - return; - } - - SetPosition (value); - } - } - - // BUGBUG: v2 - Why can't we get rid of this and just use Visible? - /// Gets or sets the visibility for the vertical or horizontal scroll indicator. - /// true if show vertical or horizontal scroll indicator; otherwise, false. - public bool ShowScrollIndicator - { - get => _showScrollIndicator && Visible; - set - { - //if (value == showScrollIndicator) { - // return; - //} - - _showScrollIndicator = value; - - if (IsInitialized) - { - SetNeedsLayout (); - - if (value) - { - Visible = true; - } - else - { - Visible = false; - Position = 0; - } - - SetWidthHeight (); - } - } - } - - /// The size of content the scrollbar represents. - /// The size. - /// - /// The is typically the size of the virtual content. E.g. when a Scrollbar is part of a - /// the Size is set to the appropriate dimension of . - /// - public int Size - { - get => _size; - set - { - _size = value; - - if (IsInitialized) - { - SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size); - ShowHideScrollBars (false); - SetNeedsLayout (); - } - } - } - - private bool _showBothScrollIndicator => OtherScrollBarView?.ShowScrollIndicator == true && ShowScrollIndicator; - - /// This event is raised when the position on the scrollbar has changed. - public event EventHandler ChangedPosition; - - /// - protected override bool OnMouseEvent (MouseEventArgs mouseEvent) - { - if (mouseEvent.Flags != MouseFlags.Button1Pressed - && mouseEvent.Flags != MouseFlags.Button1DoubleClicked - && !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) - && mouseEvent.Flags != MouseFlags.Button1Released - && mouseEvent.Flags != MouseFlags.WheeledDown - && mouseEvent.Flags != MouseFlags.WheeledUp - && mouseEvent.Flags != MouseFlags.WheeledRight - && mouseEvent.Flags != MouseFlags.WheeledLeft - && mouseEvent.Flags != MouseFlags.Button1TripleClicked) - { - return false; - } - - if (!Host.CanFocus) - { - return true; - } - - if (Host?.HasFocus == false) - { - Host.SetFocus (); - } - - int location = _vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; - int barsize = _vertical ? Viewport.Height : Viewport.Width; - int posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1; - int posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1; - barsize -= 2; - int pos = Position; - - if (mouseEvent.Flags != MouseFlags.Button1Released && (Application.MouseGrabView is null || Application.MouseGrabView != this)) - { - Application.GrabMouse (this); - } - else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView is { } && Application.MouseGrabView == this) - { - _lastLocation = -1; - Application.UngrabMouse (); - - return true; - } - - if (ShowScrollIndicator - && (mouseEvent.Flags == MouseFlags.WheeledDown - || mouseEvent.Flags == MouseFlags.WheeledUp - || mouseEvent.Flags == MouseFlags.WheeledRight - || mouseEvent.Flags == MouseFlags.WheeledLeft)) - { - return Host.NewMouseEvent (mouseEvent) == true; - } - - if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == 0) - { - if (pos > 0) - { - Position = pos - 1; - } - } - else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == barsize + 1) - { - if (CanScroll (1, out _, _vertical)) - { - Position = pos + 1; - } - } - else if (location > 0 && location < barsize + 1) - { - //var b1 = pos * (Size > 0 ? barsize / Size : 0); - //var b2 = Size > 0 - // ? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size) - // : 0; - //if (KeepContentAlwaysInViewport && b1 == b2) { - // b1 = Math.Max (b1 - 1, 0); - //} - - if (_lastLocation > -1 - || (location >= posTopLeftTee - && location <= posBottomRightTee - && mouseEvent.Flags.HasFlag ( - MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - ))) - { - if (_lastLocation == -1) - { - _lastLocation = location; - - _posBarOffset = _keepContentAlwaysInViewport - ? Math.Max (location - posTopLeftTee, 1) - : 0; - - return true; - } - - if (location > _lastLocation) - { - if (location - _posBarOffset < barsize) - { - int np = (location - _posBarOffset) * Size / barsize + Size / barsize; - - if (CanScroll (np - pos, out int nv, _vertical)) - { - Position = pos + nv; - } - } - else if (CanScroll (Size - pos, out int nv, _vertical)) - { - Position = Math.Min (pos + nv, Size); - } - } - else if (location < _lastLocation) - { - if (location - _posBarOffset > 0) - { - int np = (location - _posBarOffset) * Size / barsize - Size / barsize; - - if (CanScroll (np - pos, out int nv, _vertical)) - { - Position = pos + nv; - } - } - else - { - Position = 0; - } - } - else if (location - _posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out int nv, _vertical)) - { - Position = Math.Min (pos + nv, Size); - } - else if (location - _posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, _vertical)) - { - Position = Math.Min (pos + nv, Size); - } - else if (location - _posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) - { - Position = 0; - } - } - else if (location > posBottomRightTee) - { - if (CanScroll (barsize, out int nv, _vertical)) - { - Position = pos + nv; - } - } - else if (location < posTopLeftTee) - { - if (CanScroll (-barsize, out int nv, _vertical)) - { - Position = pos + nv; - } - } - else if (location == 1 && posTopLeftTee <= 3) - { - Position = 0; - } - else if (location == barsize) - { - if (CanScroll (Size - pos, out int nv, _vertical)) - { - Position = Math.Min (pos + nv, Size); - } - } - } - - return true; - } - - /// Virtual method to invoke the action event. - public virtual void OnChangedPosition () { ChangedPosition?.Invoke (this, EventArgs.Empty); } - - /// - protected override bool OnDrawingContent () - { - if (ColorScheme is null || ((!ShowScrollIndicator || Size == 0) && AutoHideScrollBars && Visible)) - { - if ((!ShowScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) - { - ShowHideScrollBars (false); - } - - return false; - } - - if (Size == 0 || (_vertical && Viewport.Height == 0) || (!_vertical && Viewport.Width == 0)) - { - return false; - } - - SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); - - if (_vertical) - { - if (Viewport.Right < Viewport.Width - 1) - { - return true; - } - - int col = Viewport.Width - 1; - int bh = Viewport.Height; - Rune special; - - if (bh < 4) - { - int by1 = _position * bh / Size; - int by2 = (_position + bh) * bh / Size; - - Move (col, 0); - - if (Viewport.Height == 1) - { - Driver.AddRune (Glyphs.Diamond); - } - else - { - Driver.AddRune (Glyphs.UpArrow); - } - - if (Viewport.Height == 3) - { - Move (col, 1); - Driver.AddRune (Glyphs.Diamond); - } - - if (Viewport.Height > 1) - { - Move (col, Viewport.Height - 1); - Driver.AddRune (Glyphs.DownArrow); - } - } - else - { - bh -= 2; - - int by1 = KeepContentAlwaysInViewport - ? _position * bh / Size - : _position * bh / (Size + bh); - - int by2 = KeepContentAlwaysInViewport - ? Math.Min ((_position + bh) * bh / Size + 1, bh - 1) - : (_position + bh) * bh / (Size + bh); - - if (KeepContentAlwaysInViewport && by1 == by2) - { - by1 = Math.Max (by1 - 1, 0); - } - - AddRune (col, 0, Glyphs.UpArrow); - - var hasTopTee = false; - var hasDiamond = false; - var hasBottomTee = false; - - for (var y = 0; y < bh; y++) - { - - if ((y < by1 || y > by2) && ((_position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) - { - special = Glyphs.Stipple; - } - else - { - if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) - { - hasDiamond = true; - special = Glyphs.Diamond; - } - else - { - if (y == by1 && !hasTopTee) - { - hasTopTee = true; - _posTopTee = y; - special = Glyphs.TopTee; - } - else if (((_position == 0 && y == bh - 1) || y >= by2 || by2 == 0) && !hasBottomTee) - { - hasBottomTee = true; - _posBottomTee = y; - special = Glyphs.BottomTee; - } - else - { - special = Glyphs.VLine; - } - } - } - - AddRune (col, y + 1, special); - } - - if (!hasTopTee) - { - AddRune (col, Viewport.Height - 2, Glyphs.TopTee); - } - - AddRune (col, Viewport.Height - 1, Glyphs.DownArrow); - } - } - else - { - if (Viewport.Bottom < Viewport.Height - 1) - { - return true; - } - - int row = Viewport.Height - 1; - int bw = Viewport.Width; - Rune special; - - if (bw < 4) - { - int bx1 = _position * bw / Size; - int bx2 = (_position + bw) * bw / Size; - - Move (0, row); - Driver.AddRune (Glyphs.LeftArrow); - Driver.AddRune (Glyphs.RightArrow); - } - else - { - bw -= 2; - - int bx1 = KeepContentAlwaysInViewport - ? _position * bw / Size - : _position * bw / (Size + bw); - - int bx2 = KeepContentAlwaysInViewport - ? Math.Min ((_position + bw) * bw / Size + 1, bw - 1) - : (_position + bw) * bw / (Size + bw); - - if (KeepContentAlwaysInViewport && bx1 == bx2) - { - bx1 = Math.Max (bx1 - 1, 0); - } - - Move (0, row); - Driver.AddRune (Glyphs.LeftArrow); - - var hasLeftTee = false; - var hasDiamond = false; - var hasRightTee = false; - - for (var x = 0; x < bw; x++) - { - if ((x < bx1 || x >= bx2 + 1) && ((_position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) - { - special = Glyphs.Stipple; - } - else - { - if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) - { - hasDiamond = true; - special = Glyphs.Diamond; - } - else - { - if (x == bx1 && !hasLeftTee) - { - hasLeftTee = true; - _posLeftTee = x; - special = Glyphs.LeftTee; - } - else if (((_position == 0 && x == bw - 1) || x >= bx2 || bx2 == 0) && !hasRightTee) - { - hasRightTee = true; - _posRightTee = x; - special = Glyphs.RightTee; - } - else - { - special = Glyphs.HLine; - } - } - } - - Driver.AddRune (special); - } - - if (!hasLeftTee) - { - Move (Viewport.Width - 2, row); - Driver.AddRune (Glyphs.LeftTee); - } - - Driver.AddRune (Glyphs.RightArrow); - } - } - - return false; - } - - - /// Only used for a hosted view that will update and redraw the scrollbars. - public virtual void Refresh () { ShowHideScrollBars (); } - - internal bool CanScroll (int n, out int max, bool isVertical = false) - { - if (Host?.Viewport.IsEmpty != false) - { - max = 0; - - return false; - } - - int s = GetBarsize (isVertical); - int newSize = Math.Max (Math.Min (_size - s, _position + n), 0); - max = _size > s + newSize ? newSize == 0 ? -_position : n : _size - (s + _position) - 1; - - if (_size >= s + newSize && max != 0) - { - return true; - } - - return false; - } - - private bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false) - { - int barsize = scrollBarView._vertical ? scrollBarView.Viewport.Height : scrollBarView.Viewport.Width; - - if (barsize == 0 || barsize >= scrollBarView._size) - { - if (scrollBarView.ShowScrollIndicator) - { - scrollBarView.ShowScrollIndicator = false; - } - - if (scrollBarView.Visible) - { - scrollBarView.Visible = false; - } - } - else if (barsize > 0 && barsize == scrollBarView._size && scrollBarView.OtherScrollBarView is { } && pending) - { - if (scrollBarView.ShowScrollIndicator) - { - scrollBarView.ShowScrollIndicator = false; - } - - if (scrollBarView.Visible) - { - scrollBarView.Visible = false; - } - - if (scrollBarView.OtherScrollBarView is { } && scrollBarView._showBothScrollIndicator) - { - scrollBarView.OtherScrollBarView.ShowScrollIndicator = false; - } - - if (scrollBarView.OtherScrollBarView.Visible) - { - scrollBarView.OtherScrollBarView.Visible = false; - } - } - else if (barsize > 0 && barsize == _size && scrollBarView.OtherScrollBarView is { } && !pending) - { - pending = true; - } - else - { - if (scrollBarView.OtherScrollBarView is { } && pending) - { - if (!scrollBarView._showBothScrollIndicator) - { - scrollBarView.OtherScrollBarView.ShowScrollIndicator = true; - } - - if (!scrollBarView.OtherScrollBarView.Visible) - { - scrollBarView.OtherScrollBarView.Visible = true; - } - } - - if (!scrollBarView.ShowScrollIndicator) - { - scrollBarView.ShowScrollIndicator = true; - } - - if (!scrollBarView.Visible) - { - scrollBarView.Visible = true; - } - } - - return pending; - } - - private void ContentBottomRightCorner_DrawContent (object sender, DrawEventArgs e) - { - SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); - - // I'm forced to do this here because the Clear method is - // changing the color attribute and is different of this one - Driver.FillRect (Driver.Clip.GetBounds()); - e.Cancel = true; - } - - //private void Host_CanFocusChanged () - //{ - // CanFocus = Host.CanFocus; - // if (otherScrollBarView is { }) { - // otherScrollBarView.CanFocus = CanFocus; - // } - //} - - private void ContentBottomRightCorner_MouseClick (object sender, MouseEventArgs me) - { - if (me.Flags == MouseFlags.WheeledDown - || me.Flags == MouseFlags.WheeledUp - || me.Flags == MouseFlags.WheeledRight - || me.Flags == MouseFlags.WheeledLeft) - { - NewMouseEvent (me); - } - else if (me.Flags == MouseFlags.Button1Clicked) - { - Host.SetFocus (); - } - - me.Handled = true; - } - - private void CreateBottomRightCorner (View host) - { - if (Host is null) - { - Host = host; - } - - if (Host != null - && ((_contentBottomRightCorner is null && OtherScrollBarView is null) - || (_contentBottomRightCorner is null && OtherScrollBarView is { } && OtherScrollBarView._contentBottomRightCorner is null))) - { - _contentBottomRightCorner = new ContentBottomRightCorner { Visible = Host.Visible }; - - if (_hosted) - { - Host.SuperView.Add (_contentBottomRightCorner); - _contentBottomRightCorner.X = Pos.Right (Host) - 1; - _contentBottomRightCorner.Y = Pos.Bottom (Host) - 1; - } - else - { - Host.Add (_contentBottomRightCorner); - _contentBottomRightCorner.X = Pos.AnchorEnd (1); - _contentBottomRightCorner.Y = Pos.AnchorEnd (1); - } - - _contentBottomRightCorner.Width = 1; - _contentBottomRightCorner.Height = 1; - _contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick; - _contentBottomRightCorner.DrawingContent += ContentBottomRightCorner_DrawContent; - } - } - - private int GetBarsize (bool isVertical) - { - if (Host?.Viewport.IsEmpty != false) - { - return 0; - } - - return isVertical ? KeepContentAlwaysInViewport - ? Host.Viewport.Height + (_showBothScrollIndicator ? -2 : -1) - : 0 : - KeepContentAlwaysInViewport ? Host.Viewport.Width + (_showBothScrollIndicator ? -2 : -1) : 0; - } - - private void Host_EnabledChanged (object sender, EventArgs e) - { - Enabled = Host.Enabled; - - if (_otherScrollBarView is { }) - { - _otherScrollBarView.Enabled = Enabled; - } - - _contentBottomRightCorner.Enabled = Enabled; - } - - private void Host_VisibleChanged (object sender, EventArgs e) - { - if (!Host.Visible) - { - Visible = Host.Visible; - - if (_otherScrollBarView is { }) - { - _otherScrollBarView.Visible = Visible; - } - - _contentBottomRightCorner.Visible = Visible; - } - else - { - ShowHideScrollBars (); - } - } - - private void ScrollBarView_Initialized (object sender, EventArgs e) - { - SetWidthHeight (); - SetRelativeLayout (SuperView?.Frame.Size ?? Host?.Frame.Size ?? Frame.Size); - - if (OtherScrollBarView is null) - { - // Only do this once if both scrollbars are enabled - ShowHideScrollBars (); - } - - SetPosition (Position); - } - - // Helper to assist Initialized event handler - private void SetPosition (int newPosition) - { - if (!IsInitialized) - { - // We're not initialized so we can't do anything fancy. Just cache value. - _position = newPosition; - - return; - } - - if (newPosition < 0) - { - _position = 0; - SetNeedsDraw (); - - return; - } - else if (CanScroll (newPosition - _position, out int max, _vertical)) - { - if (max == newPosition - _position) - { - _position = newPosition; - } - else - { - _position = Math.Max (_position + max, 0); - } - } - else if (max < 0) - { - _position = Math.Max (_position + max, 0); - } - else - { - _position = Math.Max (newPosition, 0); - } - - OnChangedPosition (); - SetNeedsDraw (); - } - - // BUGBUG: v2 - rationalize this with View.SetMinWidthHeight - private void SetWidthHeight () - { - // BUGBUG: v2 - If Host is also the ScrollBarView's superview, this is all bogus because it's not - // supported that a view can reference it's superview's Dims. This code also assumes the host does - // not have a margin/borderframe/padding. - if (!IsInitialized || _otherScrollBarView is { IsInitialized: false }) - { - return; - } - - if (_showBothScrollIndicator) - { - Width = _vertical ? 1 : - Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1; - Height = _vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1; - - _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : - Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1; - - _otherScrollBarView.Height = _otherScrollBarView._vertical - ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 - : 1; - } - else if (ShowScrollIndicator) - { - Width = _vertical ? 1 : - Host != SuperView ? Dim.Width (Host) : Dim.Fill (); - Height = _vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () : 1; - } - else if (_otherScrollBarView?.ShowScrollIndicator == true) - { - _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : - Host != SuperView ? Dim.Width (Host) : Dim.Fill () - 0; - - _otherScrollBarView.Height = _otherScrollBarView._vertical - ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () - 0 - : 1; - } - } - - private void ShowHideScrollBars (bool redraw = true) - { - if (!_hosted || (_hosted && !_autoHideScrollBars)) - { - if (_contentBottomRightCorner is { } && _contentBottomRightCorner.Visible) - { - _contentBottomRightCorner.Visible = false; - } - else if (_otherScrollBarView != null - && _otherScrollBarView._contentBottomRightCorner != null - && _otherScrollBarView._contentBottomRightCorner.Visible) - { - _otherScrollBarView._contentBottomRightCorner.Visible = false; - } - - return; - } - - bool pending = CheckBothScrollBars (this); - - if (_otherScrollBarView is { }) - { - CheckBothScrollBars (_otherScrollBarView, pending); - } - - SetWidthHeight (); - SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size); - - if (_otherScrollBarView is { }) - { - OtherScrollBarView.SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size); - } - - if (_showBothScrollIndicator) - { - if (_contentBottomRightCorner is { }) - { - _contentBottomRightCorner.Visible = true; - } - else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { }) - { - _otherScrollBarView._contentBottomRightCorner.Visible = true; - } - } - else if (!ShowScrollIndicator) - { - if (_contentBottomRightCorner is { }) - { - _contentBottomRightCorner.Visible = false; - } - else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { }) - { - _otherScrollBarView._contentBottomRightCorner.Visible = false; - } - - if (Application.MouseGrabView is { } && Application.MouseGrabView == this) - { - Application.UngrabMouse (); - } - } - else if (_contentBottomRightCorner is { }) - { - _contentBottomRightCorner.Visible = false; - } - else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { }) - { - _otherScrollBarView._contentBottomRightCorner.Visible = false; - } - - if (Host?.Visible == true && ShowScrollIndicator && !Visible) - { - Visible = true; - } - - if (Host?.Visible == true && _otherScrollBarView?.ShowScrollIndicator == true && !_otherScrollBarView.Visible) - { - _otherScrollBarView.Visible = true; - } - - if (!redraw) - { - return; - } - - if (ShowScrollIndicator) - { - Draw (); - } - - if (_otherScrollBarView is { } && _otherScrollBarView.ShowScrollIndicator) - { - _otherScrollBarView.Draw (); - } - - if (_contentBottomRightCorner is { } && _contentBottomRightCorner.Visible) - { - _contentBottomRightCorner.Draw (); - } - else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { } && _otherScrollBarView._contentBottomRightCorner.Visible) - { - _otherScrollBarView._contentBottomRightCorner.Draw (); - } - } - - internal class ContentBottomRightCorner : View - { - public ContentBottomRightCorner () - { - ColorScheme = ColorScheme; - } - } -} diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs deleted file mode 100644 index aeae539add..0000000000 --- a/Terminal.Gui/Views/ScrollView.cs +++ /dev/null @@ -1,774 +0,0 @@ -// -// ScrollView.cs: ScrollView view. -// -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// -// -// TODO: -// - focus in scrollview -// - focus handling in scrollview to auto scroll to focused view -// - Raise events -// - Perhaps allow an option to not display the scrollbar arrow indicators? - -using System.ComponentModel; - -namespace Terminal.Gui; - -/// -/// Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS -/// UIScrollView. -/// -/// -/// -/// The subviews that are added to this are offset by the -/// property. The view itself is a window into the space represented by the -/// . -/// -/// Use the -/// -public class ScrollView : View -{ - private readonly ContentView _contentView; - private readonly ScrollBarView _horizontal; - private readonly ScrollBarView _vertical; - private bool _autoHideScrollBars = true; - private View _contentBottomRightCorner; - private Point _contentOffset; - private bool _keepContentAlwaysInViewport = true; - private bool _showHorizontalScrollIndicator; - private bool _showVerticalScrollIndicator; - - /// - /// Initializes a new instance of the class. - /// - public ScrollView () - { - _contentView = new ContentView (); - - _vertical = new ScrollBarView - { - X = Pos.AnchorEnd (1), - Y = 0, - Width = 1, - Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0), - Size = 1, - IsVertical = true, - Host = this - }; - - _horizontal = new ScrollBarView - { - X = 0, - Y = Pos.AnchorEnd (1), - Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0), - Height = 1, - Size = 1, - IsVertical = false, - Host = this - }; - - _vertical.OtherScrollBarView = _horizontal; - _horizontal.OtherScrollBarView = _vertical; - base.Add (_contentView); - CanFocus = true; - TabStop = TabBehavior.TabGroup; - - MouseEnter += View_MouseEnter; - MouseLeave += View_MouseLeave; - _contentView.MouseEnter += View_MouseEnter; - _contentView.MouseLeave += View_MouseLeave; - - Application.UnGrabbedMouse += Application_UnGrabbedMouse; - - // Things this view knows how to do - AddCommand (Command.ScrollUp, () => ScrollUp (1)); - AddCommand (Command.ScrollDown, () => ScrollDown (1)); - AddCommand (Command.ScrollLeft, () => ScrollLeft (1)); - AddCommand (Command.ScrollRight, () => ScrollRight (1)); - AddCommand (Command.PageUp, () => ScrollUp (Viewport.Height)); - AddCommand (Command.PageDown, () => ScrollDown (Viewport.Height)); - AddCommand (Command.PageLeft, () => ScrollLeft (Viewport.Width)); - AddCommand (Command.PageRight, () => ScrollRight (Viewport.Width)); - AddCommand (Command.Start, () => ScrollUp (GetContentSize ().Height)); - AddCommand (Command.End, () => ScrollDown (GetContentSize ().Height)); - AddCommand (Command.LeftStart, () => ScrollLeft (GetContentSize ().Width)); - AddCommand (Command.RightEnd, () => ScrollRight (GetContentSize ().Width)); - - // Default keybindings for this view - KeyBindings.Add (Key.CursorUp, Command.ScrollUp); - KeyBindings.Add (Key.CursorDown, Command.ScrollDown); - KeyBindings.Add (Key.CursorLeft, Command.ScrollLeft); - KeyBindings.Add (Key.CursorRight, Command.ScrollRight); - - KeyBindings.Add (Key.PageUp, Command.PageUp); - KeyBindings.Add (Key.V.WithAlt, Command.PageUp); - - KeyBindings.Add (Key.PageDown, Command.PageDown); - KeyBindings.Add (Key.V.WithCtrl, Command.PageDown); - - KeyBindings.Add (Key.PageUp.WithCtrl, Command.PageLeft); - KeyBindings.Add (Key.PageDown.WithCtrl, Command.PageRight); - KeyBindings.Add (Key.Home, Command.Start); - KeyBindings.Add (Key.End, Command.End); - KeyBindings.Add (Key.Home.WithCtrl, Command.LeftStart); - KeyBindings.Add (Key.End.WithCtrl, Command.RightEnd); - - Initialized += (s, e) => - { - if (!_vertical.IsInitialized) - { - _vertical.BeginInit (); - _vertical.EndInit (); - } - - if (!_horizontal.IsInitialized) - { - _horizontal.BeginInit (); - _horizontal.EndInit (); - } - - SetContentOffset (_contentOffset); - _contentView.Frame = new Rectangle (ContentOffset, GetContentSize ()); - - // PERF: How about calls to Point.Offset instead? - _vertical.ChangedPosition += delegate { ContentOffset = new Point (ContentOffset.X, _vertical.Position); }; - _horizontal.ChangedPosition += delegate { ContentOffset = new Point (_horizontal.Position, ContentOffset.Y); }; - }; - ContentSizeChanged += ScrollViewContentSizeChanged; - } - - private void ScrollViewContentSizeChanged (object sender, SizeChangedEventArgs e) - { - if (e.Size is null) - { - return; - } - _contentView.Frame = new Rectangle (ContentOffset, e.Size.Value with { Width = e.Size.Value.Width - 1, Height = e.Size.Value.Height - 1 }); - _vertical.Size = e.Size.Value.Height; - _horizontal.Size = e.Size.Value.Width; - } - - private void Application_UnGrabbedMouse (object sender, ViewEventArgs e) - { - var parent = e.View is Adornment adornment ? adornment.Parent : e.View; - - if (parent is { }) - { - var supView = parent.SuperView; - - while (supView is { }) - { - if (supView == _contentView) - { - Application.GrabMouse (this); - - break; - } - - supView = supView.SuperView; - } - } - } - - /// If true the vertical/horizontal scroll bars won't be showed if it's not needed. - public bool AutoHideScrollBars - { - get => _autoHideScrollBars; - set - { - if (_autoHideScrollBars != value) - { - _autoHideScrollBars = value; - - if (Subviews.Contains (_vertical)) - { - _vertical.AutoHideScrollBars = value; - } - - if (Subviews.Contains (_horizontal)) - { - _horizontal.AutoHideScrollBars = value; - } - - SetNeedsDraw (); - } - } - } - - /// Represents the top left corner coordinate that is displayed by the scrollview - /// The content offset. - public Point ContentOffset - { - get => _contentOffset; - set - { - if (!IsInitialized) - { - // We're not initialized so we can't do anything fancy. Just cache value. - _contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y)); - - return; - } - - SetContentOffset (value); - } - } - - ///// Represents the contents of the data shown inside the scrollview - ///// The size of the content. - //public new Size ContentSize - //{ - // get => ContentSize; - // set - // { - // if (GetContentSize () != value) - // { - // ContentSize = value; - // _contentView.Frame = new Rectangle (_contentOffset, value); - // _vertical.Size = GetContentSize ().Height; - // _horizontal.Size = GetContentSize ().Width; - // SetNeedsDraw (); - // } - // } - //} - - /// Get or sets if the view-port is kept always visible in the area of this - public bool KeepContentAlwaysInViewport - { - get => _keepContentAlwaysInViewport; - set - { - if (_keepContentAlwaysInViewport != value) - { - _keepContentAlwaysInViewport = value; - _vertical.OtherScrollBarView.KeepContentAlwaysInViewport = value; - _horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value; - Point p = default; - - if (value && -_contentOffset.X + Viewport.Width > GetContentSize ().Width) - { - p = new Point ( - GetContentSize ().Width - Viewport.Width + (_showVerticalScrollIndicator ? 1 : 0), - -_contentOffset.Y - ); - } - - if (value && -_contentOffset.Y + Viewport.Height > GetContentSize ().Height) - { - if (p == default (Point)) - { - p = new Point ( - -_contentOffset.X, - GetContentSize ().Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0) - ); - } - else - { - p.Y = GetContentSize ().Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0); - } - } - - if (p != default (Point)) - { - ContentOffset = p; - } - } - } - } - - /// Gets or sets the visibility for the horizontal scroll indicator. - /// true if show horizontal scroll indicator; otherwise, false. - public bool ShowHorizontalScrollIndicator - { - get => _showHorizontalScrollIndicator; - set - { - if (value != _showHorizontalScrollIndicator) - { - _showHorizontalScrollIndicator = value; - SetNeedsLayout (); - - if (value) - { - _horizontal.OtherScrollBarView = _vertical; - base.Add (_horizontal); - _horizontal.ShowScrollIndicator = value; - _horizontal.AutoHideScrollBars = _autoHideScrollBars; - _horizontal.OtherScrollBarView.ShowScrollIndicator = value; - _horizontal.MouseEnter += View_MouseEnter; - _horizontal.MouseLeave += View_MouseLeave; - } - else - { - base.Remove (_horizontal); - _horizontal.OtherScrollBarView = null; - _horizontal.MouseEnter -= View_MouseEnter; - _horizontal.MouseLeave -= View_MouseLeave; - } - } - - _vertical.Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0); - } - } - - /// Gets or sets the visibility for the vertical scroll indicator. - /// true if show vertical scroll indicator; otherwise, false. - public bool ShowVerticalScrollIndicator - { - get => _showVerticalScrollIndicator; - set - { - if (value != _showVerticalScrollIndicator) - { - _showVerticalScrollIndicator = value; - SetNeedsLayout (); - - if (value) - { - _vertical.OtherScrollBarView = _horizontal; - base.Add (_vertical); - _vertical.ShowScrollIndicator = value; - _vertical.AutoHideScrollBars = _autoHideScrollBars; - _vertical.OtherScrollBarView.ShowScrollIndicator = value; - _vertical.MouseEnter += View_MouseEnter; - _vertical.MouseLeave += View_MouseLeave; - } - else - { - Remove (_vertical); - _vertical.OtherScrollBarView = null; - _vertical.MouseEnter -= View_MouseEnter; - _vertical.MouseLeave -= View_MouseLeave; - } - } - - _horizontal.Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0); - } - } - - /// Adds the view to the scrollview. - /// The view to add to the scrollview. - public override View Add (View view) - { - if (view is ScrollBarView.ContentBottomRightCorner) - { - _contentBottomRightCorner = view; - base.Add (view); - } - else - { - if (!IsOverridden (view, "OnMouseEvent")) - { - view.MouseEnter += View_MouseEnter; - view.MouseLeave += View_MouseLeave; - } - - _contentView.Add (view); - } - - SetNeedsLayout (); - return view; - } - - /// - protected override bool OnDrawingContent () - { - SetViewsNeedsDraw (); - - // TODO: It's bad practice for views to always clear a view. It negates clipping. - ClearViewport (); - - if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0) - { - Region? saved = ClipFrame(); - _contentView.Draw (); - View.SetClip (saved); - } - - DrawScrollBars (); - - return true; - } - - /// - protected override bool OnKeyDown (Key a) - { - if (base.OnKeyDown (a)) - { - return true; - } - - bool? result = InvokeCommands (a, KeyBindingScope.HotKey | KeyBindingScope.Focused); - - if (result is { }) - { - return (bool)result; - } - - return false; - } - - /// - protected override bool OnMouseEvent (MouseEventArgs me) - { - if (!Enabled) - { - // A disabled view should not eat mouse events - return false; - } - - if (me.Flags == MouseFlags.WheeledDown && ShowVerticalScrollIndicator) - { - return ScrollDown (1); - } - else if (me.Flags == MouseFlags.WheeledUp && ShowVerticalScrollIndicator) - { - return ScrollUp (1); - } - else if (me.Flags == MouseFlags.WheeledRight && _showHorizontalScrollIndicator) - { - return ScrollRight (1); - } - else if (me.Flags == MouseFlags.WheeledLeft && ShowVerticalScrollIndicator) - { - return ScrollLeft (1); - } - else if (me.Position.X == _vertical.Frame.X && ShowVerticalScrollIndicator) - { - _vertical.NewMouseEvent (me); - } - else if (me.Position.Y == _horizontal.Frame.Y && ShowHorizontalScrollIndicator) - { - _horizontal.NewMouseEvent (me); - } - else if (IsOverridden (me.View, "OnMouseEvent")) - { - Application.UngrabMouse (); - } - - return me.Handled; - } - - /// - public override Point? PositionCursor () - { - if (InternalSubviews.Count == 0) - { - Move (0, 0); - - return null; // Don't show the cursor - } - return base.PositionCursor (); - } - - /// Removes the view from the scrollview. - /// The view to remove from the scrollview. - public override View Remove (View view) - { - if (view is null) - { - return view; - } - - SetNeedsDraw (); - View container = view?.SuperView; - - if (container == this) - { - base.Remove (view); - } - else - { - container?.Remove (view); - } - - if (_contentView.InternalSubviews.Count < 1) - { - CanFocus = false; - } - - return view; - } - - /// Removes all widgets from this container. - public override void RemoveAll () { _contentView.RemoveAll (); } - - /// Scrolls the view down. - /// true, if left was scrolled, false otherwise. - /// Number of lines to scroll. - public bool ScrollDown (int lines) - { - if (_vertical.CanScroll (lines, out _, true)) - { - ContentOffset = new Point (_contentOffset.X, _contentOffset.Y - lines); - - return true; - } - - return false; - } - - /// Scrolls the view to the left - /// true, if left was scrolled, false otherwise. - /// Number of columns to scroll by. - public bool ScrollLeft (int cols) - { - if (_contentOffset.X < 0) - { - ContentOffset = new Point (Math.Min (_contentOffset.X + cols, 0), _contentOffset.Y); - - return true; - } - - return false; - } - - /// Scrolls the view to the right. - /// true, if right was scrolled, false otherwise. - /// Number of columns to scroll by. - public bool ScrollRight (int cols) - { - if (_horizontal.CanScroll (cols, out _)) - { - ContentOffset = new Point (_contentOffset.X - cols, _contentOffset.Y); - - return true; - } - - return false; - } - - /// Scrolls the view up. - /// true, if left was scrolled, false otherwise. - /// Number of lines to scroll. - public bool ScrollUp (int lines) - { - if (_contentOffset.Y < 0) - { - ContentOffset = new Point (_contentOffset.X, Math.Min (_contentOffset.Y + lines, 0)); - - return true; - } - - return false; - } - - /// - protected override void Dispose (bool disposing) - { - if (!_showVerticalScrollIndicator) - { - // It was not added to SuperView, so it won't get disposed automatically - _vertical?.Dispose (); - } - - if (!_showHorizontalScrollIndicator) - { - // It was not added to SuperView, so it won't get disposed automatically - _horizontal?.Dispose (); - } - - Application.UnGrabbedMouse -= Application_UnGrabbedMouse; - - base.Dispose (disposing); - } - - private void DrawScrollBars () - { - if (_autoHideScrollBars) - { - ShowHideScrollBars (); - } - else - { - if (ShowVerticalScrollIndicator) - { - Region? saved = View.SetClipToScreen (); - _vertical.Draw (); - View.SetClip (saved); - } - - if (ShowHorizontalScrollIndicator) - { - Region? saved = View.SetClipToScreen (); - _horizontal.Draw (); - View.SetClip (saved); - } - - if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator) - { - SetContentBottomRightCornerVisibility (); - Region? saved = View.SetClipToScreen (); - _contentBottomRightCorner.Draw (); - View.SetClip (saved); - } - } - } - - private void SetContentBottomRightCornerVisibility () - { - if (_showHorizontalScrollIndicator && _showVerticalScrollIndicator) - { - _contentBottomRightCorner.Visible = true; - } - else if (_horizontal.IsAdded || _vertical.IsAdded) - { - _contentBottomRightCorner.Visible = false; - } - } - - private void SetContentOffset (Point offset) - { - // INTENT: Unclear intent. How about a call to Offset? - _contentOffset = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y)); - _contentView.Frame = new Rectangle (_contentOffset, GetContentSize ()); - int p = Math.Max (0, -_contentOffset.Y); - - if (_vertical.Position != p) - { - _vertical.Position = Math.Max (0, -_contentOffset.Y); - } - - p = Math.Max (0, -_contentOffset.X); - - if (_horizontal.Position != p) - { - _horizontal.Position = Math.Max (0, -_contentOffset.X); - } - SetNeedsDraw (); - } - - private void SetViewsNeedsDraw () - { - foreach (View view in _contentView.Subviews) - { - view.SetNeedsDraw (); - } - } - - private void ShowHideScrollBars () - { - bool v = false, h = false; - var p = false; - - if (GetContentSize () is { } && (Viewport.Height == 0 || Viewport.Height > GetContentSize ().Height)) - { - if (ShowVerticalScrollIndicator) - { - ShowVerticalScrollIndicator = false; - } - - v = false; - } - else if (GetContentSize () is { } && Viewport.Height > 0 && Viewport.Height == GetContentSize ().Height) - { - p = true; - } - else - { - if (!ShowVerticalScrollIndicator) - { - ShowVerticalScrollIndicator = true; - } - - v = true; - } - - if (GetContentSize () is { } && (Viewport.Width == 0 || Viewport.Width > GetContentSize ().Width)) - { - if (ShowHorizontalScrollIndicator) - { - ShowHorizontalScrollIndicator = false; - } - - h = false; - } - else if (GetContentSize () is { } && Viewport.Width > 0 && Viewport.Width == GetContentSize ().Width && p) - { - if (ShowHorizontalScrollIndicator) - { - ShowHorizontalScrollIndicator = false; - } - - h = false; - - if (ShowVerticalScrollIndicator) - { - ShowVerticalScrollIndicator = false; - } - - v = false; - } - else - { - if (p) - { - if (!ShowVerticalScrollIndicator) - { - ShowVerticalScrollIndicator = true; - } - - v = true; - } - - if (!ShowHorizontalScrollIndicator) - { - ShowHorizontalScrollIndicator = true; - } - - h = true; - } - - Dim dim = Dim.Fill (h ? 1 : 0); - - if (!_vertical.Height.Equals (dim)) - { - _vertical.Height = dim; - } - - dim = Dim.Fill (v ? 1 : 0); - - if (!_horizontal.Width.Equals (dim)) - { - _horizontal.Width = dim; - } - - if (v) - { - _vertical.SetRelativeLayout (Viewport.Size); - _vertical.Draw (); - } - - if (h) - { - _horizontal.SetRelativeLayout (Viewport.Size); - _horizontal.Draw (); - } - - SetContentBottomRightCornerVisibility (); - - if (v && h) - { - _contentBottomRightCorner.SetRelativeLayout (Viewport.Size); - _contentBottomRightCorner.Draw (); - } - } - - private void View_MouseEnter (object sender, CancelEventArgs e) { Application.GrabMouse (this); } - - private void View_MouseLeave (object sender, EventArgs e) - { - if (Application.MouseGrabView is { } && Application.MouseGrabView != this && Application.MouseGrabView != _vertical && Application.MouseGrabView != _horizontal) - { - Application.UngrabMouse (); - } - } - - // The ContentView is the view that contains the subviews and content that are being scrolled - // The ContentView is the size of the ContentSize and is offset by the ContentOffset - private class ContentView : View - { - public ContentView () { CanFocus = true; } - } -} diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs index d7e181eae1..92398ceb65 100644 --- a/UICatalog/Scenarios/ASCIICustomButton.cs +++ b/UICatalog/Scenarios/ASCIICustomButton.cs @@ -137,7 +137,7 @@ public class ScrollViewTestWindow : Window private const int BUTTONS_ON_PAGE = 7; private readonly List