Parcourir la source

Merge pull request #861 from BDisp/scrollview-keys-fix

Fixes #859. ScrollDown and ScrollRight navigates to beyond the ContentSize size.
Charlie Kindel il y a 5 ans
Parent
commit
96208a1c49
2 fichiers modifiés avec 106 ajouts et 37 suppressions
  1. 94 33
      Terminal.Gui/Views/ScrollView.cs
  2. 12 4
      UICatalog/Scenarios/Scrolling.cs

+ 94 - 33
Terminal.Gui/Views/ScrollView.cs

@@ -74,6 +74,11 @@ namespace Terminal.Gui {
 			}
 		}
 
+		/// <summary>
+		/// Get or sets the view that host this <see cref="ScrollView"/>
+		/// </summary>
+		public ScrollView Host { get; internal set; }
+
 		void SetPosition (int newPos)
 		{
 			Position = newPos;
@@ -168,7 +173,10 @@ namespace Terminal.Gui {
 				} else {
 					bh -= 2;
 					var by1 = position * bh / Size;
-					var by2 = (position + bh) * bh / Size;
+					var by2 = Host.KeepContentAlwaysInViewport ? Math.Min (((position + bh) * bh / Size) + 1, bh - 1) : (position + bh) * bh / Size;
+					if (Host.KeepContentAlwaysInViewport && by1 == by2) {
+						by1 = Math.Max (by1 - 1, 0);
+					}
 
 					Move (col, 0);
 					Driver.AddRune (Driver.UpArrow);
@@ -191,7 +199,7 @@ namespace Terminal.Gui {
 									hasTopTee = true;
 									posTopTee = y;
 									special = Driver.TopTee;
-								} else if ((y >= by2 || by2 == 0) && !hasBottomTee) {
+								} else if ((position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) {
 									hasBottomTee = true;
 									posBottomTee = y;
 									special = Driver.BottomTee;
@@ -225,7 +233,10 @@ namespace Terminal.Gui {
 				} else {
 					bw -= 2;
 					var bx1 = position * bw / Size;
-					var bx2 = (position + bw) * bw / Size;
+					var bx2 = Host.KeepContentAlwaysInViewport ? Math.Min (((position + bw) * bw / Size) + 1, bw - 1) : (position + bw) * bw / Size;
+					if (Host.KeepContentAlwaysInViewport && bx1 == bx2) {
+						bx1 = Math.Max (bx1 - 1, 0);
+					}
 
 					Move (0, row);
 					Driver.AddRune (Driver.LeftArrow);
@@ -245,7 +256,7 @@ namespace Terminal.Gui {
 									hasLeftTee = true;
 									posLeftTee = x;
 									special = Driver.LeftTee;
-								} else if ((x >= bx2 || bx2 == 0) && !hasRightTee) {
+								} else if ((position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) {
 									hasRightTee = true;
 									posRightTee = x;
 									special = Driver.RightTee;
@@ -270,8 +281,9 @@ namespace Terminal.Gui {
 		public override bool MouseEvent (MouseEvent me)
 		{
 			if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked &&
-				!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
+				!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
 				return false;
+			}
 
 			int location = vertical ? me.Y : me.X;
 			int barsize = vertical ? Bounds.Height : Bounds.Width;
@@ -281,23 +293,27 @@ namespace Terminal.Gui {
 			barsize -= 2;
 			var pos = Position;
 			if (location == 0) {
-				if (pos > 0)
+				if (pos > 0) {
 					SetPosition (pos - 1);
+				}
 			} else if (location == barsize + 1) {
-				if (pos + 1 < Size)
+				if (Host.CanScroll (1, out _, vertical)) {
 					SetPosition (pos + 1);
-			} else {
+				}
+			} else if (location > 0 && location < barsize + 1) {
 				var b1 = pos * barsize / Size;
-				var b2 = (pos + barsize) * barsize / Size;
-
-				if (b1 == 0 && location == 1 && pos == 0 || (location >= posTopLeftTee + 1 && location <= posBottomRightTee + 1 && (pos != 0 || pos != Size - 1) && location != 1 && location != barsize) ||
-					(b2 == barsize + (b2 - b1 - 1) && location == barsize && pos == Size - 1)) {
-					return true;
-				} else if (location <= barsize) {
-					if (location > 1 && location > posTopLeftTee && location > posBottomRightTee)
-						SetPosition (Math.Min (pos + (Size / location), Size - 1));
-					else if (location <= b2 && pos > 0 || pos > 0)
-						SetPosition (Math.Max (pos - (Size / barsize), 0));
+				var b2 = Host.KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size;
+				if (Host.KeepContentAlwaysInViewport && b1 == b2) {
+					b1 = Math.Max (b1 - 1, 0);
+				}
+
+				if (location > b2 + 1 && location > posTopLeftTee && location > b1 && location > posBottomRightTee && posBottomRightTee > 0) {
+					Host.CanScroll (location, out int nv, vertical);
+					if (nv > 0) {
+						SetPosition (Math.Min (pos + nv, Size));
+					}
+				} else if (location <= b1) {
+					SetPosition (Math.Max (pos - barsize - location, 0));
 				}
 			}
 
@@ -352,6 +368,7 @@ namespace Terminal.Gui {
 			vertical.ChangedPosition += delegate {
 				ContentOffset = new Point (ContentOffset.X, vertical.Position);
 			};
+			vertical.Host = this;
 			horizontal = new ScrollBarView (1, 0, isVertical: false) {
 				X = 0,
 				Y = Pos.AnchorEnd (1),
@@ -361,6 +378,7 @@ namespace Terminal.Gui {
 			horizontal.ChangedPosition += delegate {
 				ContentOffset = new Point (horizontal.Position, ContentOffset.Y);
 			};
+			horizontal.Host = this;
 			base.Add (contentView);
 			CanFocus = true;
 
@@ -372,6 +390,7 @@ namespace Terminal.Gui {
 		Point contentOffset;
 		bool showHorizontalScrollIndicator;
 		bool showVerticalScrollIndicator;
+		bool keepContentAlwaysInViewport = true;
 
 		/// <summary>
 		/// Represents the contents of the data shown inside the scrolview
@@ -382,11 +401,13 @@ namespace Terminal.Gui {
 				return contentSize;
 			}
 			set {
-				contentSize = value;
-				contentView.Frame = new Rect (contentOffset, value);
-				vertical.Size = contentSize.Height;
-				horizontal.Size = contentSize.Width;
-				SetNeedsDisplay ();
+				if (contentSize != value) {
+					contentSize = value;
+					contentView.Frame = new Rect (contentOffset, value);
+					vertical.Size = contentSize.Height;
+					horizontal.Size = contentSize.Width;
+					SetNeedsDisplay ();
+				}
 			}
 		}
 
@@ -412,6 +433,32 @@ namespace Terminal.Gui {
 		/// </summary>
 		public bool AutoHideScrollBars { get; set; } = true;
 
+		/// <summary>
+		/// Get or sets if the view-port is kept always visible in the area of this <see cref="ScrollView"/>
+		/// </summary>
+		public bool KeepContentAlwaysInViewport {
+			get { return keepContentAlwaysInViewport; }
+			set {
+				if (keepContentAlwaysInViewport != value) {
+					keepContentAlwaysInViewport = value;
+					Point p = default;
+					if (value && -contentOffset.X + Bounds.Width > contentSize.Width) {
+						p = new Point (contentSize.Width - Bounds.Width + (showVerticalScrollIndicator ? 1 : 0), -contentOffset.Y);
+					}
+					if (value && -contentOffset.Y + Bounds.Height > contentSize.Height) {
+						if (p == default) {
+							p = new Point (-contentOffset.X, contentSize.Height - Bounds.Height + (showHorizontalScrollIndicator ? 1 : 0));
+						} else {
+							p.Y = contentSize.Height - Bounds.Height + (showHorizontalScrollIndicator ? 1 : 0);
+						}
+					}
+					if (p != default) {
+						ContentOffset = p;
+					}
+				}
+			}
+		}
+
 		/// <summary>
 		/// Adds the view to the scrollview.
 		/// </summary>
@@ -645,11 +692,11 @@ namespace Terminal.Gui {
 		/// <param name="lines">Number of lines to scroll.</param>
 		public bool ScrollDown (int lines)
 		{
-			var ny = Math.Max (-contentSize.Height, contentOffset.Y - lines);
-			if (ny == contentOffset.Y)
-				return false;
-			ContentOffset = new Point (contentOffset.X, ny);
-			return true;
+			if (CanScroll (lines, out _, true)) {
+				ContentOffset = new Point (contentOffset.X, contentOffset.Y - lines);
+				return true;
+			}
+			return false;
 		}
 
 		/// <summary>
@@ -659,12 +706,26 @@ namespace Terminal.Gui {
 		/// <param name="cols">Number of columns to scroll by.</param>
 		public bool ScrollRight (int cols)
 		{
-			var nx = Math.Max (-contentSize.Width, contentOffset.X - cols);
-			if (nx == contentOffset.X)
-				return false;
+			if (CanScroll (cols, out _)) {
+				ContentOffset = new Point (contentOffset.X - cols, contentOffset.Y);
+				return true;
+			}
+			return false;
+		}
 
-			ContentOffset = new Point (nx, contentOffset.Y);
-			return true;
+		internal bool CanScroll (int n, out int max, bool isVertical = false)
+		{
+			var size = isVertical ?
+				(KeepContentAlwaysInViewport ? Bounds.Height + (showHorizontalScrollIndicator ? -2 : -1) : 0) :
+				(KeepContentAlwaysInViewport ? Bounds.Width + (showVerticalScrollIndicator ? -2 : -1) : 0);
+			var cSize = isVertical ? -contentSize.Height : -contentSize.Width;
+			var cOffSet = isVertical ? contentOffset.Y : contentOffset.X;
+			var newSize = Math.Max (cSize, cOffSet - n);
+			max = cSize < newSize - size ? n : -cSize + (cOffSet - size) - 1;
+			if (cSize < newSize - size) {
+				return true;
+			}
+			return false;
 		}
 
 		///<inheritdoc/>

+ 12 - 4
UICatalog/Scenarios/Scrolling.cs

@@ -213,10 +213,15 @@ namespace UICatalog {
 
 			var t = "Auto Hide Scrollbars";
 			var ahCheckBox = new CheckBox (t, scrollView.AutoHideScrollBars) {
-				X = Pos.Left (scrollView) + scrollView.Bounds.Width / 2 - t.Length / 2,
+				X = Pos.Left (scrollView) + (scrollView.Bounds.Width / 2) - (t.Length / 2),
 				Y = Pos.Bottom (scrollView) + 3,
 			};
-			hCheckBox.Toggled += (previousChecked) => {
+			var k = "Keep Content Always In Viewport";
+			var keepCheckBox = new CheckBox (k, scrollView.AutoHideScrollBars) {
+				X = Pos.Left (scrollView) + (scrollView.Bounds.Width / 2) - (k.Length / 2),
+				Y = Pos.Bottom (scrollView) + 4,
+			};
+			hCheckBox.Toggled += (_) => {
 				if (!ahCheckBox.Checked) {
 					scrollView.ShowHorizontalScrollIndicator = hCheckBox.Checked;
 				} else {
@@ -224,7 +229,7 @@ namespace UICatalog {
 					MessageBox.Query ("Message", "Disable Auto Hide Scrollbars first.", "Ok");
 				}
 			};
-			vCheckBox.Toggled += (previousChecked) => {
+			vCheckBox.Toggled += (_) => {
 				if (!ahCheckBox.Checked) {
 					scrollView.ShowVerticalScrollIndicator = vCheckBox.Checked;
 				} else {
@@ -232,13 +237,16 @@ namespace UICatalog {
 					MessageBox.Query ("Message", "Disable Auto Hide Scrollbars first.", "Ok");
 				}
 			};
-			ahCheckBox.Toggled += (previousChecked) => {
+			ahCheckBox.Toggled += (_) => {
 				scrollView.AutoHideScrollBars = ahCheckBox.Checked;
 				hCheckBox.Checked = true;
 				vCheckBox.Checked = true;
 			};
 			Win.Add (ahCheckBox);
 
+			keepCheckBox.Toggled += (_) => scrollView.KeepContentAlwaysInViewport = keepCheckBox.Checked;
+			Win.Add (keepCheckBox);
+
 			var scrollView2 = new ScrollView (new Rect (55, 2, 20, 8)) {
 				ContentSize = new Size (20, 50),
 				//ContentOffset = new Point (0, 0),