浏览代码

Fixes #2959. Some of the scroll features on v1 are broken. (#2960)

* Add GrabMouseEventArgs for GrabbingMouse and UnGrabbingMouse events.

* Implementing GrabbingMouse and UnGrabbingMouse in Toplevel class.

* Fix contentBottomRightCorner to draw with the right color.

* Fix a typo.

* Fix ListView to allow scrolling.

* Add a comment to explain an extra column for the cursor at the end of line.

* Add unit tests for GrabbingMouse and UnGrabbingMouse events.

* Add unit test for a Window inside a non Toplevel.

* Removes GrabMouseEventArgs.

* Prevents throwing an exception if not subscribed.

* GrabMouse must be first called before set the dragPosition.

* Add unit test to test null on GrabbingMouse and UnGrabbingMouse.

* Fix event name.

* Prevents a toplevel to overflows his bounds to allow it can be drag.

* Fix unit test.

---------

Co-authored-by: Tig <[email protected]>
BDisp 1 年之前
父节点
当前提交
85c92f4b0d

+ 34 - 6
Terminal.Gui/Core/Application.cs

@@ -692,6 +692,16 @@ namespace Terminal.Gui {
 		/// </summary>
 		public static View MouseGrabView => mouseGrabView;
 
+		/// <summary>
+		/// Invoked when a view wants to grab the mouse; can be canceled.
+		/// </summary>
+		public static event Func<View, bool> GrabbingMouse;
+
+		/// <summary>
+		/// Invoked when a view wants ungrab the mouse; can be canceled.
+		/// </summary>
+		public static event Func<View, bool> UnGrabbingMouse;
+
 		/// <summary>
 		/// Event to be invoked when a view grab the mouse.
 		/// </summary>
@@ -711,9 +721,11 @@ namespace Terminal.Gui {
 		{
 			if (view == null)
 				return;
-			OnGrabbedMouse (view);
-			mouseGrabView = view;
-			Driver.UncookMouse ();
+			if (!OnGrabbingMouse (view)) {
+				OnGrabbedMouse (view);
+				mouseGrabView = view;
+				Driver.UncookMouse ();
+			}
 		}
 
 		/// <summary>
@@ -723,9 +735,25 @@ namespace Terminal.Gui {
 		{
 			if (mouseGrabView == null)
 				return;
-			OnUnGrabbedMouse (mouseGrabView);
-			mouseGrabView = null;
-			Driver.CookMouse ();
+			if (!OnUnGrabbingMouse (mouseGrabView)) {
+				OnUnGrabbedMouse (mouseGrabView);
+				mouseGrabView = null;
+				Driver.CookMouse ();
+			}
+		}
+
+		static bool OnGrabbingMouse (View view)
+		{
+			if (view == null || GrabbingMouse == null)
+				return false;
+			return (bool)(GrabbingMouse?.Invoke (view));
+		}
+
+		static bool OnUnGrabbingMouse (View view)
+		{
+			if (view == null || UnGrabbingMouse == null)
+				return false;
+			return (bool)(UnGrabbingMouse?.Invoke (view));
 		}
 
 		static void OnGrabbedMouse (View view)

+ 35 - 4
Terminal.Gui/Core/Toplevel.cs

@@ -220,6 +220,9 @@ namespace Terminal.Gui {
 		{
 			ColorScheme = Colors.TopLevel;
 
+			Application.GrabbingMouse += Application_GrabbingMouse;
+			Application.UnGrabbingMouse += Application_UnGrabbingMouse;
+
 			// Things this view knows how to do
 			AddCommand (Command.QuitToplevel, () => { QuitToplevel (); return true; });
 			AddCommand (Command.Suspend, () => { Driver.Suspend (); ; return true; });
@@ -255,6 +258,24 @@ namespace Terminal.Gui {
 			AddKeyBinding (Key.L | Key.CtrlMask, Command.Refresh);
 		}
 
+		private bool  Application_UnGrabbingMouse (View e)
+		{
+			if (dragPosition.HasValue) {
+				return true;
+			}
+
+			return false;
+		}
+
+		private bool Application_GrabbingMouse (View e)
+		{
+			if (Application.MouseGrabView == this && dragPosition.HasValue) {
+				return true;
+			}
+
+			return false;
+		}
+
 		/// <summary>
 		/// Invoked when the <see cref="Application.AlternateForwardKey"/> is changed.
 		/// </summary>
@@ -628,7 +649,8 @@ namespace Terminal.Gui {
 			}
 			mb = null; sb = null;
 			if (!(superView is Toplevel)) {
-				nx = x; ny = y;
+				nx = Math.Max (Math.Min (x, top.Frame.Right - 1), 0);
+				ny = Math.Max (Math.Min (y, top.Frame.Bottom - 1), 0);
 				return superView;
 			}
 			var superViewBorder = superView.Border != null ? (superView.Border.DrawMarginFrame ? 1 : 0) : 0;
@@ -835,11 +857,10 @@ namespace Terminal.Gui {
 				// Only start grabbing if the user clicks on the title bar.
 				if (mouseEvent.Y == 0 && mouseEvent.Flags == MouseFlags.Button1Pressed) {
 					start = new Point (mouseEvent.X, mouseEvent.Y);
-					dragPosition = new Point ();
+					Application.GrabMouse (this);
 					nx = mouseEvent.X - mouseEvent.OfX;
 					ny = mouseEvent.Y - mouseEvent.OfY;
 					dragPosition = new Point (nx, ny);
-					Application.GrabMouse (this);
 				}
 
 				//System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}");
@@ -872,8 +893,8 @@ namespace Terminal.Gui {
 			}
 
 			if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) {
-				Application.UngrabMouse ();
 				dragPosition = null;
+				Application.UngrabMouse ();
 			}
 
 			//System.Diagnostics.Debug.WriteLine ($"dragPosition after: {dragPosition.HasValue}");
@@ -1032,6 +1053,16 @@ namespace Terminal.Gui {
 		{
 			return MostFocused?.OnLeave (view) ?? base.OnLeave (view);
 		}
+
+		///<inheritdoc/>
+		protected override void Dispose (bool disposing)
+		{
+			Application.GrabbingMouse -= Application_GrabbingMouse;
+			Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
+
+			dragPosition = null;
+			base.Dispose (disposing);
+		}
 	}
 
 	/// <summary>

+ 5 - 3
Terminal.Gui/Views/ListView.cs

@@ -866,10 +866,10 @@ namespace Terminal.Gui {
 
 		void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0)
 		{
-			var u = TextFormatter.ClipAndJustify (ustr, width, TextAlignment.Left);
+			var u = TextFormatter.ClipAndJustify (ustr, width + start, TextAlignment.Left);
 			driver.AddStr (u);
 			width -= TextFormatter.GetTextWidth (u);
-			while (width-- > 0) {
+			while (width-- + start > 0) {
 				driver.AddRune (' ');
 			}
 		}
@@ -877,7 +877,8 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public void Render (ListView container, ConsoleDriver driver, bool marked, int item, int col, int line, int width, int start = 0)
 		{
-			container.Move (col, line);
+			var savedClip = container.ClipToBounds();
+			container.Move (col - start, line);
 			var t = src? [item];
 			if (t == null) {
 				RenderUstr (driver, ustring.Make (""), col, line, width);
@@ -890,6 +891,7 @@ namespace Terminal.Gui {
 					RenderUstr (driver, t.ToString (), col, line, width, start);
 				}
 			}
+			driver.Clip = savedClip;
 		}
 
 		/// <inheritdoc/>

+ 7 - 6
Terminal.Gui/Views/ScrollBarView.cs

@@ -116,6 +116,7 @@ namespace Terminal.Gui {
 			contentBottomRightCorner.Width = 1;
 			contentBottomRightCorner.Height = 1;
 			contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick;
+			contentBottomRightCorner.DrawContent += ContentBottomRightCorner_DrawContent;
 			ClearOnVisibleFalse = false;
 		}
 
@@ -161,6 +162,12 @@ namespace Terminal.Gui {
 			}
 		}
 
+		private void ContentBottomRightCorner_DrawContent (Rect obj)
+		{
+			Driver.SetAttribute (Host.HasFocus ? GetFocusColor () : GetNormalColor ());
+			Host.SuperView.AddRune (contentBottomRightCorner.Frame.X, contentBottomRightCorner.Frame.Y, ' ');
+		}
+
 		void Init (int size, int position, bool isVertical)
 		{
 			vertical = isVertical;
@@ -601,12 +608,6 @@ namespace Terminal.Gui {
 					Driver.AddRune (Driver.RightArrow);
 				}
 			}
-
-			if (contentBottomRightCorner != null && hosted && showBothScrollIndicator) {
-				contentBottomRightCorner.Redraw (contentBottomRightCorner.Bounds);
-			} else if (otherScrollBarView != null && otherScrollBarView.contentBottomRightCorner != null && otherScrollBarView.hosted && otherScrollBarView.showBothScrollIndicator) {
-				otherScrollBarView.contentBottomRightCorner.Redraw (otherScrollBarView.contentBottomRightCorner.Bounds);
-			}
 		}
 
 		int lastLocation = -1;

+ 1 - 1
Terminal.Gui/Views/ScrollView.cs

@@ -372,7 +372,7 @@ namespace Terminal.Gui {
 				}
 			}
 
-			// Fill in the bottom left corner
+			// Fill in the bottom right corner
 			if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator) {
 				AddRune (Bounds.Width - 1, Bounds.Height - 1, ' ');
 			}

+ 2 - 1
UICatalog/Scenarios/Editor.cs

@@ -166,7 +166,8 @@ namespace UICatalog.Scenarios {
 				_scrollBar.Size = _textView.Lines;
 				_scrollBar.Position = _textView.TopRow;
 				if (_scrollBar.OtherScrollBarView != null) {
-					_scrollBar.OtherScrollBarView.Size = _textView.Maxlength;
+					// + 1 is needed to show the cursor at the end of a line.
+					_scrollBar.OtherScrollBarView.Size = _textView.Maxlength + 1;
 					_scrollBar.OtherScrollBarView.Position = _textView.LeftColumn;
 				}
 				_scrollBar.LayoutSubviews ();

+ 57 - 1
UnitTests/Application/ApplicationTests.cs

@@ -942,16 +942,20 @@ namespace Terminal.Gui.ApplicationTests {
 		}
 
 		[Fact, AutoInitShutdown]
-		public void MouseGrabView_GrabbedMouse_UnGrabbedMouse ()
+		public void MouseGrabView_GrabbedMouse_UnGrabbedMouse_GrabbingMouse_UnGrabbingMouse ()
 		{
 			View grabView = null;
 			var count = 0;
+			var wasGrabbingMouse = false;
+			var wasUnGrabbingMouse = false;
 
 			var view1 = new View ();
 			var view2 = new View ();
 
 			Application.GrabbedMouse += Application_GrabbedMouse;
 			Application.UnGrabbedMouse += Application_UnGrabbedMouse;
+			Application.GrabbingMouse += Application_GrabbingMouse;
+			Application.UnGrabbingMouse += Application_UnGrabbingMouse;
 
 			Application.GrabMouse (view1);
 			Assert.Equal (0, count);
@@ -965,16 +969,20 @@ namespace Terminal.Gui.ApplicationTests {
 
 			Application.GrabbedMouse += Application_GrabbedMouse;
 			Application.UnGrabbedMouse += Application_UnGrabbedMouse;
+			Application.GrabbingMouse += Application_GrabbingMouse;
+			Application.UnGrabbingMouse += Application_UnGrabbingMouse;
 
 			Application.GrabMouse (view2);
 			Assert.Equal (1, count);
 			Assert.Equal (grabView, view2);
 			Assert.Equal (view2, Application.MouseGrabView);
+			Assert.True (wasGrabbingMouse);
 
 			Application.UngrabMouse ();
 			Assert.Equal (2, count);
 			Assert.Equal (grabView, view2);
 			Assert.Null (Application.MouseGrabView);
+			Assert.True (wasUnGrabbingMouse);
 
 			void Application_GrabbedMouse (View obj)
 			{
@@ -1002,6 +1010,54 @@ namespace Terminal.Gui.ApplicationTests {
 
 				Application.UnGrabbedMouse -= Application_UnGrabbedMouse;
 			}
+
+			bool Application_GrabbingMouse (View obj)
+			{
+				if (count == 0) {
+					Assert.Equal (view1, obj);
+					grabView = view1;
+				} else {
+					Assert.Equal (view2, obj);
+					grabView = view2;
+				}
+				wasGrabbingMouse = true;
+
+				Application.GrabbingMouse -= Application_GrabbingMouse;
+
+				return false;
+			}
+
+			bool Application_UnGrabbingMouse (View obj)
+			{
+				if (count == 0) {
+					Assert.Equal (view1, obj);
+					Assert.Equal (grabView, obj);
+				} else {
+					Assert.Equal (view2, obj);
+					Assert.Equal (grabView, obj);
+				}
+				wasUnGrabbingMouse = true;
+
+				Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
+
+				return false;
+			}
+		}
+
+		[Fact, AutoInitShutdown]
+		public void GrabbingMouse_UnGrabbingMouse_Does_Not_Throws_If_Null ()
+		{
+			// This is needed to unsubscribe all the toplevel static events.
+			Application.Top.Dispose ();
+
+			var view = new View ();
+			var exception = Record.Exception (() => Application.GrabMouse (view));
+			Assert.Null (exception);
+
+			Assert.Equal (view, Application.MouseGrabView);
+
+			exception = Record.Exception (() => Application.UngrabMouse ());
+			Assert.Null (exception);
 		}
 		#endregion
 	}

+ 45 - 0
UnitTests/TopLevels/WindowTests.cs

@@ -363,5 +363,50 @@ namespace Terminal.Gui.TopLevelTests {
 │                  │
 └──────────────────┘", output);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Window_On_Non_Toplevel_Superview_Cannot_Overflows_His_Bounds ()
+		{
+			var win = new Window () { Width = Dim.Fill (5), Height = Dim.Fill (5) };
+			var view = new View () { X = 3, Y = 3, Width = 10, Height = 10 };
+			view.Add (win);
+			Application.Top.Add (view);
+			Application.Begin (Application.Top);
+
+			ReflectionTools.InvokePrivate (
+				typeof (Application),
+				"ProcessMouseEvent",
+				new MouseEvent () {
+					X = 3,
+					Y = 3,
+					Flags = MouseFlags.Button1Pressed
+				});
+
+			Assert.Equal (win, Application.MouseGrabView);
+
+			ReflectionTools.InvokePrivate (
+				typeof (Application),
+				"ProcessMouseEvent",
+				new MouseEvent () {
+					X = 2,
+					Y = 2,
+					Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
+				});
+
+			Assert.Equal (win, Application.MouseGrabView);
+			Assert.Equal (new Rect (0, 0, 5, 5), win.Frame);
+
+			ReflectionTools.InvokePrivate (
+				typeof (Application),
+				"ProcessMouseEvent",
+				new MouseEvent () {
+					X = 14,
+					Y = 14,
+					Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
+				});
+
+			Assert.Equal (win, Application.MouseGrabView);
+			Assert.Equal (new Rect (4, 4, 5, 5), win.Frame);
+		}
 	}
 }

+ 32 - 0
UnitTests/Views/ListViewTests.cs

@@ -513,5 +513,37 @@ Item 4
 Item 5
 Item 6", output);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void LeftItem_TopItem_Tests ()
+		{
+			var source = new List<string> ();
+			for (int i = 0; i < 5; i++) {
+				source.Add ($"Item {i}");
+			}
+			var lv = new ListView (source) {
+				X = 1,
+				Width = 10,
+				Height = 5
+			};
+			Application.Top.Add (lv);
+			Application.Begin (Application.Top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+ Item 0
+ Item 1
+ Item 2
+ Item 3
+ Item 4", output);
+
+			lv.LeftItem = 1;
+			lv.TopItem = 1;
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+ tem 1
+ tem 2
+ tem 3
+ tem 4", output);
+		}
 	}
 }